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 86d4eda074..8842673756 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,343 @@ 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 + + - [Pull 6386](https://github.com/ReactiveX/RxJava/pull/6386): Add `doOnTerminate` to `Single`/`Maybe` for consistency. + +#### Bugfixes + + - [Pull 6405](https://github.com/ReactiveX/RxJava/pull/6405): Fix concatEager to dispose sources & clean up properly. + - [Pull 6398](https://github.com/ReactiveX/RxJava/pull/6398): Fix `window()` with start/end selector not disposing/cancelling properly. + +#### Documentation changes + + - [Pull 6377](https://github.com/ReactiveX/RxJava/pull/6377): Expand `Observable#debounce` and `Flowable#debounce` javadoc. + - [Pull 6408](https://github.com/ReactiveX/RxJava/pull/6408): Improving Javadoc of `flattenAsFlowable` and `flattenAsObservable` methods. + +### Version 2.2.6 - January 23, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.6%7C)) + +#### API enhancements + + - [Pull 6370](https://github.com/ReactiveX/RxJava/pull/6370): Add interruptible mode via the new `Schedulers.from(Executor, boolean)` overload. + +#### Bugfixes + + - [Pull 6359](https://github.com/ReactiveX/RxJava/pull/6359): Fix the error/race in `Observable.repeatWhen` due to flooding repeat signal. + - [Pull 6362 ](https://github.com/ReactiveX/RxJava/pull/6362 ): Fix `Completable.andThen(Completable)` not running on `observeOn`'s `Scheduler`. + - [Pull 6364](https://github.com/ReactiveX/RxJava/pull/6364): Fix `Flowable.publish` not requesting upon client change. + - [Pull 6371](https://github.com/ReactiveX/RxJava/pull/6371): Fix bounded `replay()` memory leak due to bad node retention. + - [Pull 6375](https://github.com/ReactiveX/RxJava/pull/6375): Don't dispose the winner of `{Single|Maybe|Completable}.amb()`. + - [Pull 6380](https://github.com/ReactiveX/RxJava/pull/6380): Fix `CompositeException.getRootCause()` detecting loops in the cause graph. + +#### Documentation changes + + - [Pull 6365](https://github.com/ReactiveX/RxJava/pull/6365): Indicate source disposal in `timeout(fallback)`. + +#### Other changes + + - [Pull 6353](https://github.com/ReactiveX/RxJava/pull/6353): Use `ignoreElement` to convert `Single` to `Completable` in the `README.md`. + +### Version 2.2.5 - December 31, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.5%7C)) + +#### Documentation changes + + - [Pull 6344](https://github.com/ReactiveX/RxJava/pull/6344): Use correct return type in JavaDocs documentation in `elementAtOrDefault`. + - [Pull 6346](https://github.com/ReactiveX/RxJava/pull/6346): Fix JavaDoc examples using markdown instead of `@code`. + +#### Wiki changes + + - [Pull 6324](https://github.com/ReactiveX/RxJava/pull/6324): Java 8 version for [Problem-Solving-Examples-in-RxJava](https://github.com/ReactiveX/RxJava/wiki/Problem-Solving-Examples-in-RxJava). + - [Pull 6343](https://github.com/ReactiveX/RxJava/pull/6343): Update [Filtering Observables](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) docs. + - [Pull 6351](https://github.com/ReactiveX/RxJava/pull/6351): Updated java example in [How-To-Use-RxJava.md](https://github.com/ReactiveX/RxJava/wiki/How-To-Use-RxJava) file with java 8 version. + +#### Other changes + + - [Pull 6313](https://github.com/ReactiveX/RxJava/pull/6313): Adding `@NonNull` annotation factory methods. + - [Pull 6335](https://github.com/ReactiveX/RxJava/pull/6335): Replace indexed loop with for-each java5 syntax. + +### Version 2.2.4 - November 23, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.4%7C)) + +#### API changes + + - [Pull 6278](https://github.com/ReactiveX/RxJava/pull/6278): Add `Maybe`/`Single`/`Completable` `materialize` operator, + - [Pull 6278](https://github.com/ReactiveX/RxJava/pull/6278): Add `Single.dematerialize(selector)` operator. + - [Pull 6281](https://github.com/ReactiveX/RxJava/pull/6281): Add `Flowable`/`Observable` `dematerialize(selector)` operator. + +#### Bugfixes + + - [Pull 6258](https://github.com/ReactiveX/RxJava/pull/6258): Fix cancel/dispose upon upstream switch for some operators. + - [Pull 6269](https://github.com/ReactiveX/RxJava/pull/6269): Call the `doOn{Dispose|Cancel}` handler at most once. + - [Pull 6283](https://github.com/ReactiveX/RxJava/pull/6283): Fix `Observable.flatMap` to sustain concurrency level. + - [Pull 6297](https://github.com/ReactiveX/RxJava/pull/6297): Fix refCount eager disconnect not resetting the connection. + +#### Documentation changes + + - [Pull 6280](https://github.com/ReactiveX/RxJava/pull/6280): Improve the package docs of `io.reactivex.schedulers`. + - [Pull 6301](https://github.com/ReactiveX/RxJava/pull/6301): Add missing `onSubscribe` null-checks to NPE docs on `Flowable`/`Observable` `subscribe`. + - [Pull 6303](https://github.com/ReactiveX/RxJava/pull/6303): Fix incorrect image placement in `Flowable.zip` docs. + - [Pull 6305](https://github.com/ReactiveX/RxJava/pull/6305): Explain the non-concurrency requirement of the `Emitter` interface methods. + - [Pull 6308](https://github.com/ReactiveX/RxJava/pull/6308): Explain the need to consume both the group sequence and each group specifically with `Flowable.groupBy`. + - [Pull 6311](https://github.com/ReactiveX/RxJava/pull/6311): Explain that `distinctUntilChanged` requires non-mutating data to work as expected. + +#### Wiki changes + + - [Pull 6260](https://github.com/ReactiveX/RxJava/pull/6260): Add `generate` examples to `Creating-Observables.md`. + - [Pull 6267](https://github.com/ReactiveX/RxJava/pull/6267): Fix `Creating-Observables.md` docs stlye mistake. + - [Pull 6273](https://github.com/ReactiveX/RxJava/pull/6273): Fix broken markdown of `How-to-Contribute.md`. + - [Pull 6266](https://github.com/ReactiveX/RxJava/pull/6266): Update Error Handling Operators docs. + - [Pull 6291](https://github.com/ReactiveX/RxJava/pull/6291): Update Transforming Observables docs. + +#### Other changes + + - [Pull 6262](https://github.com/ReactiveX/RxJava/pull/6262): Use JUnit's assert format for assert messages for better IDE interoperation. + - [Pull 6263](https://github.com/ReactiveX/RxJava/pull/6263): Inline `SubscriptionHelper.isCancelled()`. + - [Pull 6275](https://github.com/ReactiveX/RxJava/pull/6275): Improve the `Observable`/`Flowable` `cache()` operators. + - [Pull 6287](https://github.com/ReactiveX/RxJava/pull/6287): Expose the Keep-Alive value of the IO `Scheduler` as System property. + - [Pull 6321](https://github.com/ReactiveX/RxJava/pull/6321): Fix `Flowable.toObservable` backpressure annotation. + +### Version 2.2.3 - October 23, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.3%7C)) + +#### API changes + + - [Pull 6242](https://github.com/ReactiveX/RxJava/pull/6242): Add timed `Completable.delaySubscription()` operator. + +#### Documentation changes + + - [Pull 6220](https://github.com/ReactiveX/RxJava/pull/6220): Remove unnecessary 's' from `ConnectableObservable`. + - [Pull 6241](https://github.com/ReactiveX/RxJava/pull/6241): Remove mention of `io.reactivex.functions.Functions` nonexistent utility class. + +#### Other changes + + - [Pull 6232](https://github.com/ReactiveX/RxJava/pull/6232): Cleanup `Observable.flatMap` drain logic. + - [Pull 6234](https://github.com/ReactiveX/RxJava/pull/6234): Add timeout and unit to `TimeoutException` message in the `timeout` operators. + - [Pull 6236](https://github.com/ReactiveX/RxJava/pull/6236): Adjust `UndeliverableException` and `OnErrorNotImplementedException` message to use the full inner exception. + - [Pull 6244](https://github.com/ReactiveX/RxJava/pull/6244): Add `@Nullable` annotations for blocking methods in `Completable`. + +### Version 2.2.2 - September 6, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.2%7C)) + +#### Bugfixes + + - [Pull 6187](https://github.com/ReactiveX/RxJava/pull/6187): Fix `refCount` termination-reconnect race. + +#### Documentation changes + + - [Pull 6171](https://github.com/ReactiveX/RxJava/pull/6171): Add explanation text to `Undeliverable` & `OnErrorNotImplemented` exceptions. + - [Pull 6174](https://github.com/ReactiveX/RxJava/pull/6174): Auto-clean up RxJavaPlugins JavaDocs HTML. + - [Pull 6175](https://github.com/ReactiveX/RxJava/pull/6175): Explain `null` observer/subscriber return errors from `RxJavaPlugins` in detail. + - [Pull 6180](https://github.com/ReactiveX/RxJava/pull/6180): Update `Additional-Reading.md`. + - [Pull 6180](https://github.com/ReactiveX/RxJava/pull/6180): Fix `Flowable.reduce(BiFunction)` JavaDoc; the operator does not signal `NoSuchElementException`. + - [Pull 6193](https://github.com/ReactiveX/RxJava/pull/6193): Add "error handling" java docs section to `fromCallable` & co. + - [Pull 6199](https://github.com/ReactiveX/RxJava/pull/6199): Fix terminology of cancel/dispose in the JavaDocs. + - [Pull 6200](https://github.com/ReactiveX/RxJava/pull/6200): Fix `toFuture` marbles and descriptions. + +### Version 2.2.1 - August 23, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.1%7C)) + +#### API changes + + - [Pull 6143](https://github.com/ReactiveX/RxJava/pull/6143): Add `concatArrayEagerDelayError` operator (expose feature). + +#### Bugfixes + + - [Pull 6145](https://github.com/ReactiveX/RxJava/pull/6145): Fix boundary fusion of `concatMap` and `publish` operator. + - [Pull 6158](https://github.com/ReactiveX/RxJava/pull/6158): Make `Flowable.fromCallable` consistent with the other `fromCallable`s. + - [Pull 6165](https://github.com/ReactiveX/RxJava/pull/6165): Handle undeliverable error in `Completable.fromCallable` via `RxJavaPlugins`. + - [Pull 6167](https://github.com/ReactiveX/RxJava/pull/6167): Make `observeOn` not let `worker.dispose()` get called prematurely. + +#### Performance improvements + + - [Pull 6123](https://github.com/ReactiveX/RxJava/pull/6123): Improve `Completable.onErrorResumeNext` internals. + - [Pull 6121](https://github.com/ReactiveX/RxJava/pull/6121): `Flowable.onErrorResumeNext` improvements. + +#### Documentation changes + +##### JavaDocs + + - [Pull 6095](https://github.com/ReactiveX/RxJava/pull/6095): Add marbles for `Single.timer`, `Single.defer` and `Single.toXXX` operators. + - [Pull 6137](https://github.com/ReactiveX/RxJava/pull/6137): Add marbles for `Single.concat` operator. + - [Pull 6141](https://github.com/ReactiveX/RxJava/pull/6141): Add marble diagrams for various `Single` operators. + - [Pull 6152](https://github.com/ReactiveX/RxJava/pull/6152): Clarify `TestObserver.assertValueSet` in docs and via tests. + - [Pull 6155](https://github.com/ReactiveX/RxJava/pull/6155): Fix marble of `Maybe.flatMap` events to `MaybeSource`. + +##### Wiki changes + + - [Pull 6128](https://github.com/ReactiveX/RxJava/pull/6128): Remove `fromEmitter()` in wiki. + - [Pull 6133](https://github.com/ReactiveX/RxJava/pull/6133): Update `_Sidebar.md` with new order of topics. + - [Pull 6135](https://github.com/ReactiveX/RxJava/pull/6135): Initial clean up for Combining Observables docs. + - [Pull 6131](https://github.com/ReactiveX/RxJava/pull/6131): Expand `Creating-Observables.md` wiki. + - [Pull 6134](https://github.com/ReactiveX/RxJava/pull/6134): Update RxJava Android Module documentation. + - [Pull 6140](https://github.com/ReactiveX/RxJava/pull/6140): Update Mathematical and Aggregate Operators docs. + ### Version 2.2.0 - July 31, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.0%7C)) #### Summary 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 957d1a05e6..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, @@ -116,7 +124,7 @@ The preparation of dataflows by applying various intermediate operators happens ```java Flowable flow = Flowable.range(1, 5) -.map(v -> v* v) +.map(v -> v * v) .filter(v -> v % 3 == 0) ; ``` @@ -193,7 +201,7 @@ Typically, you can move computations or blocking IO to some other thread via `su ### Schedulers -RxJava operators don't work with `Thread`s or `ExecutorService`s directly but with so called `Scheduler`s that abstract away sources of concurrency behind an uniform API. RxJava 2 features several standard schedulers accessible via `Schedulers` utility class. +RxJava operators don't work with `Thread`s or `ExecutorService`s directly but with so called `Scheduler`s that abstract away sources of concurrency behind a uniform API. RxJava 2 features several standard schedulers accessible via `Schedulers` utility class. - `Schedulers.computation()`: Run computation intensive work on a fixed number of dedicated threads in the background. Most asynchronous operator use this as their default `Scheduler`. - `Schedulers.io()`: Run I/O-like or blocking operations on a dynamically changing set of threads. @@ -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 @@ -369,7 +375,7 @@ Each reactive base class features operators that can perform such conversions, i |----------|----------|------------|--------|-------|-------------| |**Flowable** | | `toObservable` | `first`, `firstOrError`, `single`, `singleOrError`, `last`, `lastOrError`1 | `firstElement`, `singleElement`, `lastElement` | `ignoreElements` | |**Observable**| `toFlowable`2 | | `first`, `firstOrError`, `single`, `singleOrError`, `last`, `lastOrError`1 | `firstElement`, `singleElement`, `lastElement` | `ignoreElements` | -|**Single** | `toFlowable`3 | `toObservable` | | `toMaybe` | `toCompletable` | +|**Single** | `toFlowable`3 | `toObservable` | | `toMaybe` | `ignoreElement` | |**Maybe** | `toFlowable`3 | `toObservable` | `toSingle` | | `ignoreElement` | |**Completable** | `toFlowable` | `toObservable` | `toSingle` | `toMaybe` | | 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 6f65b4f677..d634e956a6 100644 --- a/docs/Additional-Reading.md +++ b/docs/Additional-Reading.md @@ -1,31 +1,30 @@ -(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 +* [Introduction to Rx](http://www.introtorx.com/): a free, on-line book by Lee Campbell **(1.x)** * [The introduction to Reactive Programming you've been missing](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) by Andre Staltz -* [Mastering Observables](http://docs.couchbase.com/developer/java-2.0/observables.html) from the Couchbase documentation -* [Reactive Programming in Java 8 With RxJava](http://pluralsight.com/training/Courses/TableOfContents/reactive-programming-java-8-rxjava), a course designed by Russell Elledge -* [33rd Degree Reactive Java](http://www.slideshare.net/tkowalcz/33rd-degree-reactive-java) by Tomasz Kowalczewski +* [Mastering Observables](http://docs.couchbase.com/developer/java-2.0/observables.html) from the Couchbase documentation **(1.x)** +* [Reactive Programming in Java 8 With RxJava](http://pluralsight.com/training/Courses/TableOfContents/reactive-programming-java-8-rxjava), a course designed by Russell Elledge **(1.x)** +* [33rd Degree Reactive Java](http://www.slideshare.net/tkowalcz/33rd-degree-reactive-java) by Tomasz Kowalczewski **(1.x)** * [What Every Hipster Should Know About Functional Reactive Programming](http://www.infoq.com/presentations/game-functional-reactive-programming) - Bodil Stokke demos the creation of interactive game mechanics in RxJS * [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/) -* Grokking RxJava, [Part 1: The Basics](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/), [Part 2: Operator, Operator](http://blog.danlew.net/2014/09/22/grokking-rxjava-part-2/), [Part 3: Reactive with Benefits](http://blog.danlew.net/2014/09/30/grokking-rxjava-part-3/), [Part 4: Reactive Android](http://blog.danlew.net/2014/10/08/grokking-rxjava-part-4/) - published in Sep/Oct 2014 by Daniel Lew -* [FRP on Android](http://slides.com/yaroslavheriatovych/frponandroid#/) - publish in Jan 2014 by Yaroslav Heriatovych +* Grokking RxJava, [Part 1: The Basics](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/), [Part 2: Operator, Operator](http://blog.danlew.net/2014/09/22/grokking-rxjava-part-2/), [Part 3: Reactive with Benefits](http://blog.danlew.net/2014/09/30/grokking-rxjava-part-3/), [Part 4: Reactive Android](http://blog.danlew.net/2014/10/08/grokking-rxjava-part-4/) - published in Sep/Oct 2014 by Daniel Lew **(1.x)** # How Netflix Is Using RxJava -* LambdaJam Chicago 2013: [Functional Reactive Programming in the Netflix API](https://speakerdeck.com/benjchristensen/functional-reactive-programming-in-the-netflix-api-lambdajam-2013) by Ben Christensen -* QCon London 2013 presentation: [Functional Reactive Programming in the Netflix API](http://www.infoq.com/presentations/netflix-functional-rx) and a related [interview](http://www.infoq.com/interviews/christensen-hystrix-rxjava) with Ben Christensen -* [Functional Reactive in the Netflix API with RxJava](http://techblog.netflix.com/2013/02/rxjava-netflix-api.html) by Ben Christensen and Jafar Husain -* [Optimizing the Netflix API](http://techblog.netflix.com/2013/01/optimizing-netflix-api.html) by Ben Christensen -* [Reactive Programming at Netflix](http://techblog.netflix.com/2013/01/reactive-programming-at-netflix.html) by Jafar Husain +* LambdaJam Chicago 2013: [Functional Reactive Programming in the Netflix API](https://speakerdeck.com/benjchristensen/functional-reactive-programming-in-the-netflix-api-lambdajam-2013) by Ben Christensen **(1.x)** +* QCon London 2013 presentation: [Functional Reactive Programming in the Netflix API](http://www.infoq.com/presentations/netflix-functional-rx) and a related [interview](http://www.infoq.com/interviews/christensen-hystrix-rxjava) with Ben Christensen **(1.x)** +* [Functional Reactive in the Netflix API with RxJava](http://techblog.netflix.com/2013/02/rxjava-netflix-api.html) by Ben Christensen and Jafar Husain **(1.x)** +* [Optimizing the Netflix API](http://techblog.netflix.com/2013/01/optimizing-netflix-api.html) by Ben Christensen **(1.x)** +* [Reactive Programming at Netflix](http://techblog.netflix.com/2013/01/reactive-programming-at-netflix.html) by Jafar Husain **(1.x)** # RxScala -* [RxJava: Reactive Extensions in Scala](http://www.youtube.com/watch?v=tOMK_FYJREw&feature=youtu.be): video of Ben Christensen and Matt Jacobs presenting at SF Scala +* [RxJava: Reactive Extensions in Scala](http://www.youtube.com/watch?v=tOMK_FYJREw&feature=youtu.be): video of Ben Christensen and Matt Jacobs presenting at SF Scala **(1.x)** # Rx.NET * [rx.codeplex.com](https://rx.codeplex.com) @@ -44,7 +43,20 @@ * [RxJS](https://xgrommx.github.io/rx-book/), an on-line book by @xgrommx * [Journey from procedural to reactive Javascript with stops](https://glebbahmutov.com/blog/journey-from-procedural-to-reactive-javascript-with-stops/) by Gleb Bahmutov +# RxAndroid + +* [FRP on Android](http://slides.com/yaroslavheriatovych/frponandroid#/) - publish in Jan 2014 by Yaroslav Heriatovych **(1.x)** +* [RxAndroid Github page](https://github.com/ReactiveX/RxAndroid) **(2.x)** +* [RxAndroid basics](https://medium.com/@kurtisnusbaum/rxandroid-basics-part-1-c0d5edcf6850) **(1.x & 2.x)** +* [RxJava and RxAndroid on AndroidHive](https://www.androidhive.info/RxJava/) **(1.x & 2.x)** +* [Reactive Programming with RxAndroid in Kotlin: An Introduction](https://www.raywenderlich.com/384-reactive-programming-with-rxandroid-in-kotlin-an-introduction) **(2.x)** +* [Difference between RxJava and RxAndroid](https://stackoverflow.com/questions/49651249/difference-between-rxjava-and-rxandroid) **(2.x)** +* [Reactive programming with RxAndroid](https://www.androidauthority.com/reactive-programming-with-rxandroid-711104/) **(1.x)** +* [RxJava - Vogella.com](http://www.vogella.com/tutorials/RxJava/article.html) **(2.x)** +* [Funcitional reactive Android](https://www.toptal.com/android/functional-reactive-android-rxjava) **(1.x)** +* [Reactive Programming with RxAndroid and Kotlin](https://www.pluralsight.com/courses/rxandroid-kotlin-reactive-programming) + # Miscellany -* [RxJava Observables and Akka Actors](http://onoffswitch.net/rxjava-observables-akka-actors/) by Anton Kropp -* [Vert.x and RxJava](http://slid.es/petermd/eclipsecon2014) by @petermd -* [RxJava in Different Flavours of Java](http://instil.co/2014/08/05/rxjava-in-different-flavours-of-java/): Java 7 and Java 8 implementations of the same code \ No newline at end of file +* [RxJava Observables and Akka Actors](http://onoffswitch.net/rxjava-observables-akka-actors/) by Anton Kropp **(1.x & 2.x)** +* [Vert.x and RxJava](http://slid.es/petermd/eclipsecon2014) by @petermd **(1.x)** +* [RxJava in Different Flavours of Java](http://instil.co/2014/08/05/rxjava-in-different-flavours-of-java/): Java 7 and Java 8 implementations of the same code **(1.x)** 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/Combining-Observables.md b/docs/Combining-Observables.md index 0e3796da1f..bcc19f6b0f 100644 --- a/docs/Combining-Observables.md +++ b/docs/Combining-Observables.md @@ -15,7 +15,7 @@ This section explains operators you can use to combine multiple Observables. **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/startwith.html](http://reactivex.io/documentation/operators/startwith.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/startwith.html](http://reactivex.io/documentation/operators/startwith.html) Emit a specified sequence of items before beginning to emit the items from the Observable. @@ -37,7 +37,7 @@ Combines multiple Observables into one. **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/merge.html](http://reactivex.io/documentation/operators/merge.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/merge.html](http://reactivex.io/documentation/operators/merge.html) Combines multiple Observables into one. Any `onError` notifications passed from any of the source observables will immediately be passed through to through to the observers and will terminate the merged `Observable`. @@ -55,7 +55,7 @@ Observable.just(1, 2, 3) **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/merge.html](http://reactivex.io/documentation/operators/merge.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/merge.html](http://reactivex.io/documentation/operators/merge.html) Combines multiple Observables into one. Any `onError` notifications passed from any of the source observables will be withheld until all merged Observables complete, and only then will be passed along to the observers. @@ -75,7 +75,7 @@ Observable.mergeDelayError(observable1, observable2) **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/zip.html](http://reactivex.io/documentation/operators/zip.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/zip.html](http://reactivex.io/documentation/operators/zip.html) Combines sets of items emitted by two or more Observables together via a specified function and emit items based on the results of this function. @@ -94,7 +94,7 @@ firstNames.zipWith(lastNames, (first, last) -> first + " " + last) **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/combinelatest.html](http://reactivex.io/documentation/operators/combinelatest.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/combinelatest.html](http://reactivex.io/documentation/operators/combinelatest.html) 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. @@ -124,7 +124,7 @@ Observable.combineLatest(newsRefreshes, weatherRefreshes, **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/switch.html](http://reactivex.io/documentation/operators/switch.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/switch.html](http://reactivex.io/documentation/operators/switch.html) Convert an Observable that emits Observables into a single Observable that emits the items emitted by the most-recently emitted of those Observables. diff --git a/docs/Creating-Observables.md b/docs/Creating-Observables.md index b167c4d866..360e03ab09 100644 --- a/docs/Creating-Observables.md +++ b/docs/Creating-Observables.md @@ -7,6 +7,7 @@ This page shows methods that create reactive sources, such as `Observable`s. - [`empty`](#empty) - [`error`](#error) - [`from`](#from) +- [`generate`](#generate) - [`interval`](#interval) - [`just`](#just) - [`never`](#never) @@ -17,7 +18,7 @@ This page shows methods that create reactive sources, such as `Observable`s. **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/just.html](http://reactivex.io/documentation/operators/just.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/just.html](http://reactivex.io/documentation/operators/just.html) Constructs a reactive type by taking a pre-existing object and emitting that specific object to the downstream consumer upon subscription. @@ -36,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 @@ -46,7 +47,7 @@ Constructs a sequence from a pre-existing source or generator type. *Note: These static methods use the postfix naming convention (i.e., the argument type is repeated in the method name) to avoid overload resolution ambiguities.* -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/from.html](http://reactivex.io/documentation/operators/from.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/from.html](http://reactivex.io/documentation/operators/from.html) ### fromIterable @@ -79,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")); @@ -154,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); @@ -199,11 +200,32 @@ observable.subscribe( () -> System.out.println("Done")); ``` +## generate + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/create.html](http://reactivex.io/documentation/operators/create.html) + +Creates a cold, synchronous and stateful generator of values. + +#### generate example: + +```java +int startValue = 1; +int incrementValue = 1; +Flowable flowable = Flowable.generate(() -> startValue, (s, emitter) -> { + int nextValue = s + incrementValue; + emitter.onNext(nextValue); + return nextValue; +}); +flowable.subscribe(value -> System.out.println(value)); +``` + ## create **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/create.html](http://reactivex.io/documentation/operators/create.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/create.html](http://reactivex.io/documentation/operators/create.html) Construct a **safe** reactive type instance which when subscribed to by a consumer, runs an user-provided function and provides a type-specific `Emitter` for this function to generate the signal(s) the designated business logic requires. This method allows bridging the non-reactive, usually listener/callback-style world, with the reactive world. @@ -239,7 +261,7 @@ executor.shutdown(); **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/defer.html](http://reactivex.io/documentation/operators/defer.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/defer.html](http://reactivex.io/documentation/operators/defer.html) Calls an user-provided `java.util.concurrent.Callable` when a consumer subscribes to the reactive type so that the `Callable` can generate the actual reactive instance to relay signals from towards the consumer. `defer` allows: @@ -266,7 +288,7 @@ observable.subscribe(time -> System.out.println(time)); **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/range.html](http://reactivex.io/documentation/operators/range.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/range.html](http://reactivex.io/documentation/operators/range.html) Generates a sequence of values to each individual consumer. The `range()` method generates `Integer`s, the `rangeLong()` generates `Long`s. @@ -276,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()); ``` @@ -287,7 +309,7 @@ characters.subscribe(character -> System.out.print(character), erro -> error.pri **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/interval.html](http://reactivex.io/documentation/operators/interval.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/interval.html](http://reactivex.io/documentation/operators/interval.html) Periodically generates an infinite, ever increasing numbers (of type `Long`). The `intervalRange` variant generates a limited amount of such numbers. @@ -309,7 +331,7 @@ clock.subscribe(time -> { **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/timer.html](http://reactivex.io/documentation/operators/timer.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/timer.html](http://reactivex.io/documentation/operators/timer.html) After the specified time, this reactive source signals a single `0L` (then completes for `Flowable` and `Observable`). @@ -325,7 +347,7 @@ eggTimer.blockingSubscribe(v -> System.out.println("Egg is ready!")); **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/empty-never-throw.html](http://reactivex.io/documentation/operators/empty-never-throw.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/empty-never-throw.html](http://reactivex.io/documentation/operators/empty-never-throw.html) This type of source signals completion immediately upon subscription. @@ -344,7 +366,7 @@ empty.subscribe( **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/empty-never-throw.html](http://reactivex.io/documentation/operators/empty-never-throw.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/empty-never-throw.html](http://reactivex.io/documentation/operators/empty-never-throw.html) This type of source does not signal any `onNext`, `onSuccess`, `onError` or `onComplete`. This type of reactive source is useful in testing or "disabling" certain sources in combinator operators. @@ -363,7 +385,7 @@ never.subscribe( **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/empty-never-throw.html](http://reactivex.io/documentation/operators/empty-never-throw.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/empty-never-throw.html](http://reactivex.io/documentation/operators/empty-never-throw.html) Signal an error, either pre-existing or generated via a `java.util.concurrent.Callable`, to the consumer. @@ -374,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!")); ``` @@ -401,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/Error-Handling-Operators.md b/docs/Error-Handling-Operators.md index 8acae59787..0b2eace61f 100644 --- a/docs/Error-Handling-Operators.md +++ b/docs/Error-Handling-Operators.md @@ -1,14 +1,284 @@ -There are a variety of operators that you can use to react to or recover from `onError` notifications from Observables. For example, you might: +There are a variety of operators that you can use to react to or recover from `onError` notifications from reactive sources, such as `Observable`s. For example, you might: 1. swallow the error and switch over to a backup Observable to continue the sequence 1. swallow the error and emit a default item 1. swallow the error and immediately try to restart the failed Observable 1. swallow the error and try to restart the failed Observable after some back-off interval -The following pages explain these operators. +# Outline -* [**`onErrorResumeNext( )`**](http://reactivex.io/documentation/operators/catch.html) — instructs an Observable to emit a sequence of items if it encounters an error -* [**`onErrorReturn( )`**](http://reactivex.io/documentation/operators/catch.html) — instructs an Observable to emit a particular item when it encounters an error -* [**`onExceptionResumeNext( )`**](http://reactivex.io/documentation/operators/catch.html) — instructs an Observable to continue emitting items after it encounters an exception (but not another variety of throwable) -* [**`retry( )`**](http://reactivex.io/documentation/operators/retry.html) — if a source Observable emits an error, resubscribe to it in the hopes that it will complete without error -* [**`retryWhen( )`**](http://reactivex.io/documentation/operators/retry.html) — if a source Observable emits an error, pass that error to another Observable to determine whether to resubscribe to the source \ No newline at end of file +- [`doOnError`](#doonerror) +- [`onErrorComplete`](#onerrorcomplete) +- [`onErrorResumeNext`](#onerrorresumenext) +- [`onErrorReturn`](#onerrorreturn) +- [`onErrorReturnItem`](#onerrorreturnitem) +- [`onExceptionResumeNext`](#onexceptionresumenext) +- [`retry`](#retry) +- [`retryUntil`](#retryuntil) +- [`retryWhen`](#retrywhen) + +## doOnError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/do.html](http://reactivex.io/documentation/operators/do.html) + +Instructs a reactive type to invoke the given `io.reactivex.functions.Consumer` when it encounters an error. + +### doOnError example + +```java +Observable.error(new IOException("Something went wrong")) + .doOnError(error -> System.err.println("The error message is: " + error.getMessage())) + .subscribe( + x -> System.out.println("onNext should never be printed!"), + Throwable::printStackTrace, + () -> System.out.println("onComplete should never be printed!")); +``` + +## onErrorComplete + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/catch.html](http://reactivex.io/documentation/operators/catch.html) + +Instructs a reactive type to swallow an error event and replace it by a completion event. + +Optionally, a `io.reactivex.functions.Predicate` can be specified that gives more control over when an error event should be replaced by a completion event, and when not. + +### onErrorComplete example + +```java +Completable.fromAction(() -> { + throw new IOException(); +}).onErrorComplete(error -> { + // Only ignore errors of type java.io.IOException. + return error instanceof IOException; +}).subscribe( + () -> System.out.println("IOException was ignored"), + error -> System.err.println("onError should not be printed!")); +``` + +## onErrorResumeNext + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/catch.html](http://reactivex.io/documentation/operators/catch.html) + +Instructs a reactive type to emit a sequence of items if it encounters an error. + +### onErrorResumeNext example + +```java + Observable numbers = Observable.generate(() -> 1, (state, emitter) -> { + emitter.onNext(state); + + return state + 1; +}); + +numbers.scan(Math::multiplyExact) + .onErrorResumeNext(Observable.empty()) + .subscribe( + System.out::println, + error -> System.err.println("onError should not be printed!")); + +// prints: +// 1 +// 2 +// 6 +// 24 +// 120 +// 720 +// 5040 +// 40320 +// 362880 +// 3628800 +// 39916800 +// 479001600 +``` + +## onErrorReturn + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/catch.html](http://reactivex.io/documentation/operators/catch.html) + +Instructs a reactive type to emit the item returned by the specified `io.reactivex.functions.Function` when it encounters an error. + +### onErrorReturn example + +```java +Single.just("2A") + .map(v -> Integer.parseInt(v, 10)) + .onErrorReturn(error -> { + if (error instanceof NumberFormatException) return 0; + else throw new IllegalArgumentException(); + }) + .subscribe( + System.out::println, + error -> System.err.println("onError should not be printed!")); + +// prints 0 +``` + +## onErrorReturnItem + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/catch.html](http://reactivex.io/documentation/operators/catch.html) + +Instructs a reactive type to emit a particular item when it encounters an error. + +### onErrorReturnItem example + +```java +Single.just("2A") + .map(v -> Integer.parseInt(v, 10)) + .onErrorReturnItem(0) + .subscribe( + System.out::println, + error -> System.err.println("onError should not be printed!")); + +// prints 0 +``` + +## onExceptionResumeNext + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/catch.html](http://reactivex.io/documentation/operators/catch.html) + +Instructs a reactive type to continue emitting items after it encounters an `java.lang.Exception`. Unlike [`onErrorResumeNext`](#onerrorresumenext), this one lets other types of `Throwable` continue through. + +### onExceptionResumeNext example + +```java +Observable exception = Observable.error(IOException::new) + .onExceptionResumeNext(Observable.just("This value will be used to recover from the IOException")); + +Observable error = Observable.error(Error::new) + .onExceptionResumeNext(Observable.just("This value will not be used")); + +Observable.concat(exception, error) + .subscribe( + message -> System.out.println("onNext: " + message), + err -> System.err.println("onError: " + err)); + +// prints: +// onNext: This value will be used to recover from the IOException +// onError: java.lang.Error +``` + +## retry + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/retry.html](http://reactivex.io/documentation/operators/retry.html) + +Instructs a reactive type to resubscribe to the source reactive type if it encounters an error in the hopes that it will complete without error. + +### retry example + +```java +Observable source = Observable.interval(0, 1, TimeUnit.SECONDS) + .flatMap(x -> { + if (x >= 2) return Observable.error(new IOException("Something went wrong!")); + else return Observable.just(x); + }); + +source.retry((retryCount, error) -> retryCount < 3) + .blockingSubscribe( + x -> System.out.println("onNext: " + x), + error -> System.err.println("onError: " + error.getMessage())); + +// prints: +// onNext: 0 +// onNext: 1 +// onNext: 0 +// onNext: 1 +// onNext: 0 +// onNext: 1 +// onError: Something went wrong! +``` + +## retryUntil + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/retry.html](http://reactivex.io/documentation/operators/retry.html) + +Instructs a reactive type to resubscribe to the source reactive type if it encounters an error until the given `io.reactivex.functions.BooleanSupplier` returns `true`. + +### retryUntil example + +```java +LongAdder errorCounter = new LongAdder(); +Observable source = Observable.interval(0, 1, TimeUnit.SECONDS) + .flatMap(x -> { + if (x >= 2) return Observable.error(new IOException("Something went wrong!")); + else return Observable.just(x); + }) + .doOnError((error) -> errorCounter.increment()); + +source.retryUntil(() -> errorCounter.intValue() >= 3) + .blockingSubscribe( + x -> System.out.println("onNext: " + x), + error -> System.err.println("onError: " + error.getMessage())); + +// prints: +// onNext: 0 +// onNext: 1 +// onNext: 0 +// onNext: 1 +// onNext: 0 +// onNext: 1 +// onError: Something went wrong! +``` + +## retryWhen + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/retry.html](http://reactivex.io/documentation/operators/retry.html) + +Instructs a reactive type to pass any error to another `Observable` or `Flowable` to determine whether to resubscribe to the source. + +### retryWhen example + +```java +Observable source = Observable.interval(0, 1, TimeUnit.SECONDS) + .flatMap(x -> { + if (x >= 2) return Observable.error(new IOException("Something went wrong!")); + else return Observable.just(x); + }); + +source.retryWhen(errors -> { + return errors.map(error -> 1) + + // Count the number of errors. + .scan(Math::addExact) + + .doOnNext(errorCount -> System.out.println("No. of errors: " + errorCount)) + + // Limit the maximum number of retries. + .takeWhile(errorCount -> errorCount < 3) + + // Signal resubscribe event after some delay. + .flatMapSingle(errorCount -> Single.timer(errorCount, TimeUnit.SECONDS)); +}).blockingSubscribe( + x -> System.out.println("onNext: " + x), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: 0 +// onNext: 1 +// No. of errors: 1 +// onNext: 0 +// onNext: 1 +// No. of errors: 2 +// onNext: 0 +// onNext: 1 +// No. of errors: 3 +// onComplete +``` diff --git a/docs/Filtering-Observables.md b/docs/Filtering-Observables.md index 7e12d91094..620800dc8c 100644 --- a/docs/Filtering-Observables.md +++ b/docs/Filtering-Observables.md @@ -1,22 +1,760 @@ -This page shows operators you can use to filter and select items emitted by Observables. - -* [**`filter( )`**](http://reactivex.io/documentation/operators/filter.html) — filter items emitted by an Observable -* [**`takeLast( )`**](http://reactivex.io/documentation/operators/takelast.html) — only emit the last _n_ items emitted by an Observable -* [**`last( )`**](http://reactivex.io/documentation/operators/last.html) — emit only the last item emitted by an Observable -* [**`lastOrDefault( )`**](http://reactivex.io/documentation/operators/last.html) — emit only the last item emitted by an Observable, or a default value if the source Observable is empty -* [**`takeLastBuffer( )`**](http://reactivex.io/documentation/operators/takelast.html) — emit the last _n_ items emitted by an Observable, as a single list item -* [**`skip( )`**](http://reactivex.io/documentation/operators/skip.html) — ignore the first _n_ items emitted by an Observable -* [**`skipLast( )`**](http://reactivex.io/documentation/operators/skiplast.html) — ignore the last _n_ items emitted by an Observable -* [**`take( )`**](http://reactivex.io/documentation/operators/take.html) — emit only the first _n_ items emitted by an Observable -* [**`first( )` and `takeFirst( )`**](http://reactivex.io/documentation/operators/first.html) — emit only the first item emitted by an Observable, or the first item that meets some condition -* [**`firstOrDefault( )`**](http://reactivex.io/documentation/operators/first.html) — 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 -* [**`elementAt( )`**](http://reactivex.io/documentation/operators/elementat.html) — emit item _n_ emitted by the source Observable -* [**`elementAtOrDefault( )`**](http://reactivex.io/documentation/operators/elementat.html) — emit item _n_ emitted by the source Observable, or a default item if the source Observable emits fewer than _n_ items -* [**`sample( )` or `throttleLast( )`**](http://reactivex.io/documentation/operators/sample.html) — emit the most recent items emitted by an Observable within periodic time intervals -* [**`throttleFirst( )`**](http://reactivex.io/documentation/operators/sample.html) — emit the first items emitted by an Observable within periodic time intervals -* [**`throttleWithTimeout( )` or `debounce( )`**](http://reactivex.io/documentation/operators/debounce.html) — only emit an item from the source Observable after a particular timespan has passed without the Observable emitting any other items -* [**`timeout( )`**](http://reactivex.io/documentation/operators/timeout.html) — emit items from a source Observable, but issue an exception if no item is emitted in a specified timespan -* [**`distinct( )`**](http://reactivex.io/documentation/operators/distinct.html) — suppress duplicate items emitted by the source Observable -* [**`distinctUntilChanged( )`**](http://reactivex.io/documentation/operators/distinct.html) — suppress duplicate consecutive items emitted by the source Observable -* [**`ofType( )`**](http://reactivex.io/documentation/operators/filter.html) — emit only those items from the source Observable that are of a particular class -* [**`ignoreElements( )`**](http://reactivex.io/documentation/operators/ignoreelements.html) — discard the items emitted by the source Observable and only pass through the error or completed notification +This page shows operators you can use to filter and select items emitted by reactive sources, such as `Observable`s. + +# Outline + +- [`debounce`](#debounce) +- [`distinct`](#distinct) +- [`distinctUntilChanged`](#distinctuntilchanged) +- [`elementAt`](#elementat) +- [`elementAtOrError`](#elementatorerror) +- [`filter`](#filter) +- [`first`](#first) +- [`firstElement`](#firstelement) +- [`firstOrError`](#firstorerror) +- [`ignoreElement`](#ignoreelement) +- [`ignoreElements`](#ignoreelements) +- [`last`](#last) +- [`lastElement`](#lastelement) +- [`lastOrError`](#lastorerror) +- [`ofType`](#oftype) +- [`sample`](#sample) +- [`skip`](#skip) +- [`skipLast`](#skiplast) +- [`take`](#take) +- [`takeLast`](#takelast) +- [`throttleFirst`](#throttlefirst) +- [`throttleLast`](#throttlelast) +- [`throttleLatest`](#throttlelatest) +- [`throttleWithTimeout`](#throttlewithtimeout) +- [`timeout`](#timeout) + +## debounce + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/debounce.html](http://reactivex.io/documentation/operators/debounce.html) + +Drops items emitted by a reactive source that are followed by newer items before the given timeout value expires. The timer resets on each emission. + +This operator keeps track of the most recent emitted item, and emits this item only when enough time has passed without the source emitting any other items. + +### debounce example + +```java +// Diagram: +// -A--------------B----C-D-------------------E-|----> +// a---------1s +// b---------1s +// c---------1s +// d---------1s +// e-|----> +// -----------A---------------------D-----------E-|--> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(1_500); + emitter.onNext("B"); + + Thread.sleep(500); + emitter.onNext("C"); + + Thread.sleep(250); + emitter.onNext("D"); + + Thread.sleep(2_000); + emitter.onNext("E"); + emitter.onComplete(); +}); + +source.subscribeOn(Schedulers.io()) + .debounce(1, TimeUnit.SECONDS) + .blockingSubscribe( + item -> System.out.println("onNext: " + item), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: A +// onNext: D +// onNext: E +// onComplete +``` + +## distinct + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/distinct.html](http://reactivex.io/documentation/operators/distinct.html) + +Filters a reactive source by only emitting items that are distinct by comparison from previous items. A `io.reactivex.functions.Function` can be specified that projects each item emitted by the source into a new value that will be used for comparison with previous projected values. + +### distinct example + +```java +Observable.just(2, 3, 4, 4, 2, 1) + .distinct() + .subscribe(System.out::println); + +// prints: +// 2 +// 3 +// 4 +// 1 +``` + +## distinctUntilChanged + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/distinct.html](http://reactivex.io/documentation/operators/distinct.html) + +Filters a reactive source by only emitting items that are distinct by comparison from their immediate predecessors. A `io.reactivex.functions.Function` can be specified that projects each item emitted by the source into a new value that will be used for comparison with previous projected values. Alternatively, a `io.reactivex.functions.BiPredicate` can be specified that is used as the comparator function to compare immediate predecessors with each other. + +### distinctUntilChanged example + +```java +Observable.just(1, 1, 2, 1, 2, 3, 3, 4) + .distinctUntilChanged() + .subscribe(System.out::println); + +// prints: +// 1 +// 2 +// 1 +// 2 +// 3 +// 4 +``` + +## elementAt + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/elementat.html](http://reactivex.io/documentation/operators/elementat.html) + +Emits the single item at the specified zero-based index in a sequence of emissions from a reactive source. A default item can be specified that will be emitted if the specified index is not within the sequence. + +### elementAt example + +```java +Observable source = Observable.generate(() -> 1L, (state, emitter) -> { + emitter.onNext(state); + + return state + 1L; +}).scan((product, x) -> product * x); + +Maybe element = source.elementAt(5); +element.subscribe(System.out::println); + +// prints 720 +``` + +## elementAtOrError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/elementat.html](http://reactivex.io/documentation/operators/elementat.html) + +Emits the single item at the specified zero-based index in a sequence of emissions from a reactive source, or signals a `java.util.NoSuchElementException` if the specified index is not within the sequence. + +### elementAtOrError example + +```java +Observable source = Observable.just("Kirk", "Spock", "Chekov", "Sulu"); +Single element = source.elementAtOrError(4); + +element.subscribe( + name -> System.out.println("onSuccess will not be printed!"), + error -> System.out.println("onError: " + error)); + +// prints: +// onError: java.util.NoSuchElementException +``` + +## filter + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/filter.html](http://reactivex.io/documentation/operators/filter.html) + +Filters items emitted by a reactive source by only emitting those that satisfy a specified predicate. + +### filter example + +```java +Observable.just(1, 2, 3, 4, 5, 6) + .filter(x -> x % 2 == 0) + .subscribe(System.out::println); + +// prints: +// 2 +// 4 +// 6 +``` + +## first + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/first.html](http://reactivex.io/documentation/operators/first.html) + +Emits only the first item emitted by a reactive source, or emits the given default item if the source completes without emitting an item. This differs from [`firstElement`](#firstelement) in that this operator returns a `Single` whereas [`firstElement`](#firstelement) returns a `Maybe`. + +### first example + +```java +Observable source = Observable.just("A", "B", "C"); +Single firstOrDefault = source.first("D"); + +firstOrDefault.subscribe(System.out::println); + +// prints A +``` + +## firstElement + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/first.html](http://reactivex.io/documentation/operators/first.html) + +Emits only the first item emitted by a reactive source, or just completes if the source completes without emitting an item. This differs from [`first`](#first) in that this operator returns a `Maybe` whereas [`first`](#first) returns a `Single`. + +### firstElement example + +```java +Observable source = Observable.just("A", "B", "C"); +Maybe first = source.firstElement(); + +first.subscribe(System.out::println); + +// prints A +``` + +## firstOrError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/first.html](http://reactivex.io/documentation/operators/first.html) + +Emits only the first item emitted by a reactive source, or signals a `java.util.NoSuchElementException` if the source completes without emitting an item. + +### firstOrError example + +```java +Observable emptySource = Observable.empty(); +Single firstOrError = emptySource.firstOrError(); + +firstOrError.subscribe( + element -> System.out.println("onSuccess will not be printed!"), + error -> System.out.println("onError: " + error)); + +// prints: +// onError: java.util.NoSuchElementException +``` + +## ignoreElement + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/ignoreelements.html](http://reactivex.io/documentation/operators/ignoreelements.html) + +Ignores the single item emitted by a `Single` or `Maybe` source, and returns a `Completable` that signals only the error or completion event from the the source. + +### ignoreElement example + +```java +Single source = Single.timer(1, TimeUnit.SECONDS); +Completable completable = source.ignoreElement(); + +completable.doOnComplete(() -> System.out.println("Done!")) + .blockingAwait(); + +// prints (after 1 second): +// Done! +``` + +## ignoreElements + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/ignoreelements.html](http://reactivex.io/documentation/operators/ignoreelements.html) + +Ignores all items from the `Observable` or `Flowable` source, and returns a `Completable` that signals only the error or completion event from the source. + +### ignoreElements example + +```java +Observable source = Observable.intervalRange(1, 5, 1, 1, TimeUnit.SECONDS); +Completable completable = source.ignoreElements(); + +completable.doOnComplete(() -> System.out.println("Done!")) + .blockingAwait(); + +// prints (after 5 seconds): +// Done! +``` + +## last + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/last.html](http://reactivex.io/documentation/operators/last.html) + +Emits only the last item emitted by a reactive source, or emits the given default item if the source completes without emitting an item. This differs from [`lastElement`](#lastelement) in that this operator returns a `Single` whereas [`lastElement`](#lastelement) returns a `Maybe`. + +### last example + +```java +Observable source = Observable.just("A", "B", "C"); +Single lastOrDefault = source.last("D"); + +lastOrDefault.subscribe(System.out::println); + +// prints C +``` + +## lastElement + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/last.html](http://reactivex.io/documentation/operators/last.html) + +Emits only the last item emitted by a reactive source, or just completes if the source completes without emitting an item. This differs from [`last`](#last) in that this operator returns a `Maybe` whereas [`last`](#last) returns a `Single`. + +### lastElement example + +```java +Observable source = Observable.just("A", "B", "C"); +Maybe last = source.lastElement(); + +last.subscribe(System.out::println); + +// prints C +``` + +## lastOrError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/last.html](http://reactivex.io/documentation/operators/last.html) + +Emits only the last item emitted by a reactive source, or signals a `java.util.NoSuchElementException` if the source completes without emitting an item. + +### lastOrError example + +```java +Observable emptySource = Observable.empty(); +Single lastOrError = emptySource.lastOrError(); + +lastOrError.subscribe( + element -> System.out.println("onSuccess will not be printed!"), + error -> System.out.println("onError: " + error)); + +// prints: +// onError: java.util.NoSuchElementException +``` + +## ofType + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/filter.html](http://reactivex.io/documentation/operators/filter.html) + +Filters items emitted by a reactive source by only emitting those of the specified type. + +### ofType example + +```java +Observable numbers = Observable.just(1, 4.0, 3, 2.71, 2f, 7); +Observable integers = numbers.ofType(Integer.class); + +integers.subscribe((Integer x) -> System.out.println(x)); + +// prints: +// 1 +// 3 +// 7 +``` + +## sample + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sample.html](http://reactivex.io/documentation/operators/sample.html) + +Filters items emitted by a reactive source by only emitting the most recently emitted item within periodic time intervals. + +### sample example + +```java +// Diagram: +// -A----B-C-------D-----E-|--> +// -0s-----c--1s---d----2s-|--> +// -----------C---------D--|--> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(500); + emitter.onNext("B"); + + Thread.sleep(200); + emitter.onNext("C"); + + Thread.sleep(800); + emitter.onNext("D"); + + Thread.sleep(600); + emitter.onNext("E"); + emitter.onComplete(); +}); + +source.subscribeOn(Schedulers.io()) + .sample(1, TimeUnit.SECONDS) + .blockingSubscribe( + item -> System.out.println("onNext: " + item), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: C +// onNext: D +// onComplete +``` + +## skip + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/skip.html](http://reactivex.io/documentation/operators/skip.html) + +Drops the first *n* items emitted by a reactive source, and emits the remaining items. + +### skip example + +```java +Observable source = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + +source.skip(4) + .subscribe(System.out::println); + +// prints: +// 5 +// 6 +// 7 +// 8 +// 9 +// 10 +``` + +## skipLast + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/skiplast.html](http://reactivex.io/documentation/operators/skiplast.html) + +Drops the last *n* items emitted by a reactive source, and emits the remaining items. + +### skipLast example + +```java +Observable source = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + +source.skipLast(4) + .subscribe(System.out::println); + +// prints: +// 1 +// 2 +// 3 +// 4 +// 5 +// 6 +``` + +## take + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/take.html](http://reactivex.io/documentation/operators/take.html) + +Emits only the first *n* items emitted by a reactive source. + +### take example + +```java +Observable source = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + +source.take(4) + .subscribe(System.out::println); + +// prints: +// 1 +// 2 +// 3 +// 4 +``` + +## takeLast + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/takelast.html](http://reactivex.io/documentation/operators/takelast.html) + +Emits only the last *n* items emitted by a reactive source. + +### takeLast example + +```java +Observable source = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + +source.takeLast(4) + .subscribe(System.out::println); + +// prints: +// 7 +// 8 +// 9 +// 10 +``` + +## throttleFirst + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sample.html](http://reactivex.io/documentation/operators/sample.html) + +Emits only the first item emitted by a reactive source during sequential time windows of a specified duration. + +### throttleFirst example + +```java +// Diagram: +// -A----B-C-------D-----E-|--> +// a---------1s +// d-------|--> +// -A--------------D-------|--> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(500); + emitter.onNext("B"); + + Thread.sleep(200); + emitter.onNext("C"); + + Thread.sleep(800); + emitter.onNext("D"); + + Thread.sleep(600); + emitter.onNext("E"); + emitter.onComplete(); +}); + +source.subscribeOn(Schedulers.io()) + .throttleFirst(1, TimeUnit.SECONDS) + .blockingSubscribe( + item -> System.out.println("onNext: " + item), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: A +// onNext: D +// onComplete +``` + +## throttleLast + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sample.html](http://reactivex.io/documentation/operators/sample.html) + +Emits only the last item emitted by a reactive source during sequential time windows of a specified duration. + +### throttleLast example + +```java +// Diagram: +// -A----B-C-------D-----E-|--> +// -0s-----c--1s---d----2s-|--> +// -----------C---------D--|--> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(500); + emitter.onNext("B"); + + Thread.sleep(200); + emitter.onNext("C"); + + Thread.sleep(800); + emitter.onNext("D"); + + Thread.sleep(600); + emitter.onNext("E"); + emitter.onComplete(); +}); + +source.subscribeOn(Schedulers.io()) + .throttleLast(1, TimeUnit.SECONDS) + .blockingSubscribe( + item -> System.out.println("onNext: " + item), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: C +// onNext: D +// onComplete +``` + +## throttleLatest + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sample.html](http://reactivex.io/documentation/operators/sample.html) + +Emits the next item emitted by a reactive source, then periodically emits the latest item (if any) when the specified timeout elapses between them. + +### throttleLatest example + +```java +// Diagram: +// -A----B-C-------D-----E-|--> +// -a------c--1s +// -----d----1s +// -e-|--> +// -A---------C---------D--|--> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(500); + emitter.onNext("B"); + + Thread.sleep(200); + emitter.onNext("C"); + + Thread.sleep(800); + emitter.onNext("D"); + + Thread.sleep(600); + emitter.onNext("E"); + emitter.onComplete(); +}); + +source.subscribeOn(Schedulers.io()) + .throttleLatest(1, TimeUnit.SECONDS) + .blockingSubscribe( + item -> System.out.println("onNext: " + item), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: A +// onNext: C +// onNext: D +// onComplete +``` + +## throttleWithTimeout + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/debounce.html](http://reactivex.io/documentation/operators/debounce.html) + +> Alias to [debounce](#debounce) + +Drops items emitted by a reactive source that are followed by newer items before the given timeout value expires. The timer resets on each emission. + +### throttleWithTimeout example + +```java +// Diagram: +// -A--------------B----C-D-------------------E-|----> +// a---------1s +// b---------1s +// c---------1s +// d---------1s +// e-|----> +// -----------A---------------------D-----------E-|--> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(1_500); + emitter.onNext("B"); + + Thread.sleep(500); + emitter.onNext("C"); + + Thread.sleep(250); + emitter.onNext("D"); + + Thread.sleep(2_000); + emitter.onNext("E"); + emitter.onComplete(); +}); + +source.subscribeOn(Schedulers.io()) + .throttleWithTimeout(1, TimeUnit.SECONDS) + .blockingSubscribe( + item -> System.out.println("onNext: " + item), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: A +// onNext: D +// onNext: E +// onComplete +``` + +## timeout + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/timeout.html](http://reactivex.io/documentation/operators/timeout.html) + +Emits the items from the `Observable` or `Flowable` source, but terminates with a `java.util.concurrent.TimeoutException` if the next item is not emitted within the specified timeout duration starting from the previous item. For `Maybe`, `Single` and `Completable` the specified timeout duration specifies the maximum time to wait for a success or completion event to arrive. If the `Maybe`, `Single` or `Completable` does not complete within the given time a `java.util.concurrent.TimeoutException` will be emitted. + +### timeout example + +```java +// Diagram: +// -A-------B---C-----------D-|--> +// a---------1s +// b---------1s +// c---------1s +// -A-------B---C---------X------> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(800); + emitter.onNext("B"); + + Thread.sleep(400); + emitter.onNext("C"); + + Thread.sleep(1200); + emitter.onNext("D"); + emitter.onComplete(); +}); + +source.timeout(1, TimeUnit.SECONDS) + .subscribe( + item -> System.out.println("onNext: " + item), + error -> System.out.println("onError: " + error), + () -> System.out.println("onComplete will not be printed!")); + +// prints: +// onNext: A +// onNext: B +// onNext: C +// onError: java.util.concurrent.TimeoutException: The source did not signal an event for 1 seconds and has been terminated. +``` diff --git a/docs/How-To-Use-RxJava.md b/docs/How-To-Use-RxJava.md index d10274d959..16d156caa9 100644 --- a/docs/How-To-Use-RxJava.md +++ b/docs/How-To-Use-RxJava.md @@ -11,15 +11,20 @@ You can find additional code examples in the `/src/examples` folders of each [la ### Java ```java -public static void hello(String... names) { - Observable.from(names).subscribe(new Action1() { - - @Override - public void call(String s) { - System.out.println("Hello " + s + "!"); - } +public static void hello(String... args) { + Flowable.fromArray(args).subscribe(s -> System.out.println("Hello " + s + "!")); +} +``` - }); +If your platform doesn't support Java 8 lambdas (yet), you have to create an inner class of ```Consumer``` manually: +```java +public static void hello(String... args) { + Flowable.fromArray(args).subscribe(new Consumer() { + @Override + public void accept(String s) { + System.out.println("Hello " + s + "!"); + } + }); } ``` @@ -397,4 +402,4 @@ myModifiedObservable = myObservable.onErrorResumeNext({ t -> Throwable myThrowable = myCustomizedThrowableCreator(t); return (Observable.error(myThrowable)); }); -``` \ No newline at end of file +``` diff --git a/docs/How-to-Contribute.md b/docs/How-to-Contribute.md index 1b63812764..d7c4a3ceb7 100644 --- a/docs/How-to-Contribute.md +++ b/docs/How-to-Contribute.md @@ -1,18 +1,18 @@ -RxJava is still a work in progress and has a long list of work documented in the [[Issues|https://github.com/ReactiveX/RxJava/issues]]. +RxJava is still a work in progress and has a long list of work documented in the [Issues](https://github.com/ReactiveX/RxJava/issues). If you wish to contribute we would ask that you: -- read [[Rx Design Guidelines|http://blogs.msdn.com/b/rxteam/archive/2010/10/28/rx-design-guidelines.aspx]] +- read [Rx Design Guidelines](http://blogs.msdn.com/b/rxteam/archive/2010/10/28/rx-design-guidelines.aspx) - review existing code and comply with existing patterns and idioms - include unit tests - stick to Rx contracts as defined by the Rx.Net implementation when porting operators (each issue attempts to reference the correct documentation from MSDN) -Information about licensing can be found at: [[CONTRIBUTING|https://github.com/ReactiveX/RxJava/blob/1.x/CONTRIBUTING.md]]. +Information about licensing can be found at: [CONTRIBUTING](https://github.com/ReactiveX/RxJava/blob/2.x/CONTRIBUTING.md). ## How to import the project into Eclipse Two options below: -###Import as Eclipse project +### Import as Eclipse project ./gradlew eclipse @@ -23,7 +23,7 @@ In Eclipse * Right click on the project in Package Explorer, select Properties - Java Compiler - Errors/Warnings - click Enable project specific settings. * Still in Errors/Warnings, go to Deprecated and restricted API and set Forbidden reference (access-rules) to Warning. -###Import as Gradle project +### Import as Gradle project You need the Gradle plugin for Eclipse installed. diff --git a/docs/Mathematical-and-Aggregate-Operators.md b/docs/Mathematical-and-Aggregate-Operators.md index 574111ad0e..f6bf79019e 100644 --- a/docs/Mathematical-and-Aggregate-Operators.md +++ b/docs/Mathematical-and-Aggregate-Operators.md @@ -39,7 +39,7 @@ import hu.akarnokd.rxjava2.math.MathFlowable; **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/average.html](http://reactivex.io/documentation/operators/average.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/average.html](http://reactivex.io/documentation/operators/average.html) Calculates the average of `Number`s emitted by an `Observable` and emits this average as a `Double`. @@ -56,7 +56,7 @@ MathObservable.averageDouble(numbers).subscribe((Double avg) -> System.out.print **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/average.html](http://reactivex.io/documentation/operators/average.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/average.html](http://reactivex.io/documentation/operators/average.html) Calculates the average of `Number`s emitted by an `Observable` and emits this average as a `Float`. @@ -73,7 +73,7 @@ MathObservable.averageFloat(numbers).subscribe((Float avg) -> System.out.println **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/max.html](http://reactivex.io/documentation/operators/max.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/max.html](http://reactivex.io/documentation/operators/max.html) Emits the maximum value emitted by a source `Observable`. A `Comparator` can be specified that will be used to compare the elements emitted by the `Observable`. @@ -100,7 +100,7 @@ MathObservable.max(names, Comparator.comparingInt(String::length)) **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/min.html](http://reactivex.io/documentation/operators/min.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/min.html](http://reactivex.io/documentation/operators/min.html) Emits the minimum value emitted by a source `Observable`. A `Comparator` can be specified that will be used to compare the elements emitted by the `Observable`. @@ -117,7 +117,7 @@ MathObservable.min(numbers).subscribe(System.out::println); **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/sum.html](http://reactivex.io/documentation/operators/sum.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sum.html](http://reactivex.io/documentation/operators/sum.html) Adds the `Double`s emitted by an `Observable` and emits this sum. @@ -134,7 +134,7 @@ MathObservable.sumDouble(numbers).subscribe((Double sum) -> System.out.println(s **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/sum.html](http://reactivex.io/documentation/operators/sum.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sum.html](http://reactivex.io/documentation/operators/sum.html) Adds the `Float`s emitted by an `Observable` and emits this sum. @@ -151,7 +151,7 @@ MathObservable.sumFloat(numbers).subscribe((Float sum) -> System.out.println(sum **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/sum.html](http://reactivex.io/documentation/operators/sum.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sum.html](http://reactivex.io/documentation/operators/sum.html) Adds the `Integer`s emitted by an `Observable` and emits this sum. @@ -168,7 +168,7 @@ MathObservable.sumInt(numbers).subscribe((Integer sum) -> System.out.println(sum **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/sum.html](http://reactivex.io/documentation/operators/sum.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sum.html](http://reactivex.io/documentation/operators/sum.html) Adds the `Long`s emitted by an `Observable` and emits this sum. @@ -189,7 +189,7 @@ MathObservable.sumLong(numbers).subscribe((Long sum) -> System.out.println(sum)) **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/count.html](http://reactivex.io/documentation/operators/count.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/count.html](http://reactivex.io/documentation/operators/count.html) Counts the number of items emitted by an `Observable` and emits this count as a `Long`. @@ -205,7 +205,7 @@ Observable.just(1, 2, 3).count().subscribe(System.out::println); **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/reduce.html](http://reactivex.io/documentation/operators/reduce.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/reduce.html](http://reactivex.io/documentation/operators/reduce.html) Apply a function to each emitted item, sequentially, and emit only the final accumulated value. @@ -223,7 +223,7 @@ Observable.range(1, 5) **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/reduce.html](http://reactivex.io/documentation/operators/reduce.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/reduce.html](http://reactivex.io/documentation/operators/reduce.html) Apply a function to each emitted item, sequentially, and emit only the final accumulated value. @@ -245,7 +245,7 @@ Observable.just(1, 2, 2, 3, 4, 4, 4, 5) **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/reduce.html](http://reactivex.io/documentation/operators/reduce.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/reduce.html](http://reactivex.io/documentation/operators/reduce.html) Collect items emitted by the source `Observable` into a single mutable data structure and return an `Observable` that emits this structure. @@ -264,7 +264,7 @@ Observable.just("Kirk", "Spock", "Chekov", "Sulu") **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/reduce.html](http://reactivex.io/documentation/operators/reduce.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/reduce.html](http://reactivex.io/documentation/operators/reduce.html) Collect items emitted by the source `Observable` into a single mutable data structure and return an `Observable` that emits this structure. @@ -285,7 +285,7 @@ Observable.just('R', 'x', 'J', 'a', 'v', 'a') **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/to.html](http://reactivex.io/documentation/operators/to.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/to.html](http://reactivex.io/documentation/operators/to.html) Collect all items from an `Observable` and emit them as a single `List`. @@ -303,7 +303,7 @@ Observable.just(2, 1, 3) **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/to.html](http://reactivex.io/documentation/operators/to.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/to.html](http://reactivex.io/documentation/operators/to.html) Collect all items from an `Observable` and emit them as a single, sorted `List`. @@ -321,7 +321,7 @@ Observable.just(2, 1, 3) **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/to.html](http://reactivex.io/documentation/operators/to.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/to.html](http://reactivex.io/documentation/operators/to.html) Convert the sequence of items emitted by an `Observable` into a `Map` keyed by a specified key function. @@ -345,7 +345,7 @@ Observable.just(1, 2, 3, 4) **Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` -**ReactiveX doumentation:** [http://reactivex.io/documentation/operators/to.html](http://reactivex.io/documentation/operators/to.html) +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/to.html](http://reactivex.io/documentation/operators/to.html) Convert the sequence of items emitted by an `Observable` into a `Collection` that is also a `Map` keyed by a specified key function. diff --git a/docs/Problem-Solving-Examples-in-RxJava.md b/docs/Problem-Solving-Examples-in-RxJava.md index 1b41c2a122..6b848ae693 100644 --- a/docs/Problem-Solving-Examples-in-RxJava.md +++ b/docs/Problem-Solving-Examples-in-RxJava.md @@ -1,4 +1,4 @@ -This page will present some elementary RxJava puzzles and walk through some solutions (using the Groovy language implementation of RxJava) as a way of introducing you to some of the RxJava operators. +This page will present some elementary RxJava puzzles and walk through some solutions as a way of introducing you to some of the RxJava operators. # Project Euler problem #1 @@ -7,10 +7,22 @@ There used to be a site called "Project Euler" that presented a series of mathem > If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below 1000. There are several ways we could go about this with RxJava. We might, for instance, begin by going through all of the natural numbers below 1000 with [`range`](Creating-Observables#range) and then [`filter`](Filtering-Observables#filter) out those that are not a multiple either of 3 or of 5: +### Java +```java +Observable threesAndFives = Observable.range(1, 999).filter(e -> e % 3 == 0 || e % 5 == 0); +``` +### Groovy ````groovy def threesAndFives = Observable.range(1,999).filter({ !((it % 3) && (it % 5)) }); ```` Or, we could generate two Observable sequences, one containing the multiples of three and the other containing the multiples of five (by [`map`](https://github.com/Netflix/RxJava/wiki/Transforming-Observables#map)ping each value onto its appropriate multiple), making sure to only generating new multiples while they are less than 1000 (the [`takeWhile`](Conditional-and-Boolean-Operators#takewhile-and-takewhilewithindex) operator will help here), and then [`merge`](Combining-Observables#merge) these sets: +### Java +```java +Observable threes = Observable.range(1, 999).map(e -> e * 3).takeWhile(e -> e < 1000); +Observable fives = Observable.range(1, 999).map(e -> e * 5).takeWhile(e -> e < 1000); +Observable threesAndFives = Observable.merge(threes, fives).distinct(); +``` +### Groovy ````groovy def threes = Observable.range(1,999).map({it*3}).takeWhile({it<1000}); def fives = Observable.range(1,999).map({it*5}).takeWhile({it<1000}); @@ -21,6 +33,11 @@ Don't forget the [`distinct`](Filtering-Observables#distinct) operator here, oth Next, we want to sum up the numbers in the resulting sequence. If you have installed the optional `rxjava-math` module, this is elementary: just use an operator like [`sumInteger` or `sumLong`](Mathematical-and-Aggregate-Operators#suminteger-sumlong-sumfloat-and-sumdouble) on the `threesAndFives` Observable. But what if you don't have this module? How could you use standard RxJava operators to sum up a sequence and emit that sum? There are a number of operators that reduce a sequence emitted by a source Observable to a single value emitted by the resulting Observable. Most of the ones that are not in the `rxjava-math` module emit boolean evaluations of the sequence; we want something that can emit a number. The [`reduce`](Mathematical-and-Aggregate-Operators#reduce) operator will do the job: +### Java +```java +Single summer = threesAndFives.reduce(0, (a, b) -> a + b); +``` +### Groovy ````groovy def summer = threesAndFives.reduce(0, { a, b -> a+b }); ```` @@ -38,6 +55,12 @@ Here is how `reduce` gets the job done. It starts with 0 as a seed. Then, with e Finally, we want to see the result. This means we must [subscribe](Observable#onnext-oncompleted-and-onerror) to the Observable we have constructed: + +### Java +```java +summer.subscribe(System.out::print); +``` +### Groovy ````groovy summer.subscribe({println(it);}); ```` @@ -47,11 +70,24 @@ summer.subscribe({println(it);}); How could you create an Observable that emits [the Fibonacci sequence](http://en.wikipedia.org/wiki/Fibonacci_number)? The most direct way would be to use the [`create`](Creating-Observables#wiki-create) operator to make an Observable "from scratch," and then use a traditional loop within the closure you pass to that operator to generate the sequence. Something like this: +### Java +```java +Observable fibonacci = Observable.create(emitter -> { + int f1 = 0, f2 = 1, f = 1; + while (!emitter.isDisposed()) { + emitter.onNext(f); + f = f1 + f2; + f1 = f2; + f2 = f; + } +}); +``` +### Groovy ````groovy -def fibonacci = Observable.create({ observer -> - def f1=0; f2=1, f=1; - while(!observer.isUnsubscribed() { - observer.onNext(f); +def fibonacci = Observable.create({ emitter -> + def f1=0, f2=1, f=1; + while(!emitter.isDisposed()) { + emitter.onNext(f); f = f1+f2; f1 = f2; f2 = f; @@ -61,7 +97,16 @@ def fibonacci = Observable.create({ observer -> But this is a little too much like ordinary linear programming. Is there some way we can instead create this sequence by composing together existing Observable operators? Here's an option that does this: -```` +### Java +```java +Observable fibonacci = + Observable.fromArray(0) + .repeat() + .scan(new int[]{0, 1}, (a, b) -> new int[]{a[1], a[0] + a[1]}) + .map(a -> a[1]); +``` +### Groovy +````groovy def fibonacci = Observable.from(0).repeat().scan([0,1], { a,b -> [a[1], a[0]+a[1]] }).map({it[1]}); ```` It's a little [janky](http://www.urbandictionary.com/define.php?term=janky). Let's walk through it: @@ -73,6 +118,11 @@ This has the effect of emitting the following sequence of items: `[0,1], [1,1], The second item in this array describes the Fibonacci sequence. We can use `map` to reduce the sequence to just that item. To print out a portion of this sequence (using either method), you would use code like the following: +### Java +```java +fibonacci.take(15).subscribe(System.out::println); +``` +### Groovy ````groovy fibonnaci.take(15).subscribe({println(it)})]; ```` diff --git a/docs/Transforming-Observables.md b/docs/Transforming-Observables.md index aa7b53e89f..3a3d94cd2e 100644 --- a/docs/Transforming-Observables.md +++ b/docs/Transforming-Observables.md @@ -1,10 +1,777 @@ -This page shows operators with which you can transform items that are emitted by an Observable. - -* [**`map( )`**](http://reactivex.io/documentation/operators/map.html) — transform the items emitted by an Observable by applying a function to each of them -* [**`flatMap( )`, `concatMap( )`, and `flatMapIterable( )`**](http://reactivex.io/documentation/operators/flatmap.html) — transform the items emitted by an Observable into Observables (or Iterables), then flatten this into a single Observable -* [**`switchMap( )`**](http://reactivex.io/documentation/operators/flatmap.html) — transform the items emitted by an Observable into Observables, and mirror those items emitted by the most-recently transformed Observable -* [**`scan( )`**](http://reactivex.io/documentation/operators/scan.html) — apply a function to each item emitted by an Observable, sequentially, and emit each successive value -* [**`groupBy( )`**](http://reactivex.io/documentation/operators/groupby.html) — divide an Observable into a set of Observables that emit groups of items from the original Observable, organized by key -* [**`buffer( )`**](http://reactivex.io/documentation/operators/buffer.html) — periodically gather items from an Observable into bundles and emit these bundles rather than emitting the items one at a time -* [**`window( )`**](http://reactivex.io/documentation/operators/window.html) — periodically subdivide items from an Observable into Observable windows and emit these windows rather than emitting the items one at a time -* [**`cast( )`**](http://reactivex.io/documentation/operators/map.html) — cast all items from the source Observable into a particular type before reemitting them \ No newline at end of file +This page shows operators with which you can transform items that are emitted by reactive sources, such as `Observable`s. + +# Outline + +- [`buffer`](#buffer) +- [`cast`](#cast) +- [`concatMap`](#concatmap) +- [`concatMapCompletable`](#concatmapcompletable) +- [`concatMapCompletableDelayError`](#concatmapcompletabledelayerror) +- [`concatMapDelayError`](#concatmapdelayerror) +- [`concatMapEager`](#concatmapeager) +- [`concatMapEagerDelayError`](#concatmapeagerdelayerror) +- [`concatMapIterable`](#concatmapiterable) +- [`concatMapMaybe`](#concatmapmaybe) +- [`concatMapMaybeDelayError`](#concatmapmaybedelayerror) +- [`concatMapSingle`](#concatmapsingle) +- [`concatMapSingleDelayError`](#concatmapsingledelayerror) +- [`flatMap`](#flatmap) +- [`flatMapCompletable`](#flatmapcompletable) +- [`flatMapIterable`](#flatmapiterable) +- [`flatMapMaybe`](#flatmapmaybe) +- [`flatMapObservable`](#flatmapobservable) +- [`flatMapPublisher`](#flatmappublisher) +- [`flatMapSingle`](#flatmapsingle) +- [`flatMapSingleElement`](#flatmapsingleelement) +- [`flattenAsFlowable`](#flattenasflowable) +- [`flattenAsObservable`](#flattenasobservable) +- [`groupBy`](#groupby) +- [`map`](#map) +- [`scan`](#scan) +- [`switchMap`](#switchmap) +- [`window`](#window) + +## buffer + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/buffer.html](http://reactivex.io/documentation/operators/buffer.html) + +Collects the items emitted by a reactive source into buffers, and emits these buffers. + +### buffer example + +```java +Observable.range(0, 10) + .buffer(4) + .subscribe((List buffer) -> System.out.println(buffer)); + +// prints: +// [0, 1, 2, 3] +// [4, 5, 6, 7] +// [8, 9] +``` + +## cast + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/map.html](http://reactivex.io/documentation/operators/map.html) + +Converts each item emitted by a reactive source to the specified type, and emits these items. + +### cast example + +```java +Observable numbers = Observable.just(1, 4.0, 3f, 7, 12, 4.6, 5); + +numbers.filter((Number x) -> Integer.class.isInstance(x)) + .cast(Integer.class) + .subscribe((Integer x) -> System.out.println(x)); + +// prints: +// 1 +// 7 +// 12 +// 5 +``` + +## concatMap + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a reactive source, and emits the items that result from concatenating the results of these function applications. + +### concatMap example + +```java +Observable.range(0, 5) + .concatMap(i -> { + long delay = Math.round(Math.random() * 2); + + return Observable.timer(delay, TimeUnit.SECONDS).map(n -> i); + }) + .blockingSubscribe(System.out::print); + +// prints 01234 +``` + +## concatMapCompletable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.CompletableSource`, subscribes to them one at a time and returns a `Completable` that completes when all sources completed. + +### concatMapCompletable example + +```java +Observable source = Observable.just(2, 1, 3); +Completable completable = source.concatMapCompletable(x -> { + return Completable.timer(x, TimeUnit.SECONDS) + .doOnComplete(() -> System.out.println("Info: Processing of item \"" + x + "\" completed")); + }); + +completable.doOnComplete(() -> System.out.println("Info: Processing of all items completed")) + .blockingAwait(); + +// prints: +// Info: Processing of item "2" completed +// Info: Processing of item "1" completed +// Info: Processing of item "3" completed +// Info: Processing of all items completed +``` + +## concatMapCompletableDelayError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.CompletableSource`, subscribes to them one at a time and returns a `Completable` that completes when all sources completed. Any errors from the sources will be delayed until all of them terminate. + +### concatMapCompletableDelayError example + +```java +Observable source = Observable.just(2, 1, 3); +Completable completable = source.concatMapCompletableDelayError(x -> { + if (x.equals(2)) { + return Completable.error(new IOException("Processing of item \"" + x + "\" failed!")); + } else { + return Completable.timer(1, TimeUnit.SECONDS) + .doOnComplete(() -> System.out.println("Info: Processing of item \"" + x + "\" completed")); + } +}); + +completable.doOnError(error -> System.out.println("Error: " + error.getMessage())) + .onErrorComplete() + .blockingAwait(); + +// prints: +// Info: Processing of item "1" completed +// Info: Processing of item "3" completed +// Error: Processing of item "2" failed! +``` + +## concatMapDelayError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a reactive source, and emits the items that result from concatenating the results of these function applications. Any errors from the sources will be delayed until all of them terminate. + +### concatMapDelayError example + +```java +Observable.intervalRange(1, 3, 0, 1, TimeUnit.SECONDS) + .concatMapDelayError(x -> { + if (x.equals(1L)) return Observable.error(new IOException("Something went wrong!")); + else return Observable.just(x, x * x); + }) + .blockingSubscribe( + x -> System.out.println("onNext: " + x), + error -> System.out.println("onError: " + error.getMessage())); + +// prints: +// onNext: 2 +// onNext: 4 +// onNext: 3 +// onNext: 9 +// onError: Something went wrong! +``` + +## concatMapEager + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a reactive source, and emits the items that result from concatenating the results of these function applications. Unlike [`concatMap`](#concatmap), this operator eagerly subscribes to all sources. + +### concatMapEager example + +```java +Observable.range(0, 5) + .concatMapEager(i -> { + long delay = Math.round(Math.random() * 3); + + return Observable.timer(delay, TimeUnit.SECONDS) + .map(n -> i) + .doOnNext(x -> System.out.println("Info: Finished processing item " + x)); + }) + .blockingSubscribe(i -> System.out.println("onNext: " + i)); + +// prints (lines beginning with "Info..." can be displayed in a different order): +// Info: Finished processing item 2 +// Info: Finished processing item 0 +// onNext: 0 +// Info: Finished processing item 1 +// onNext: 1 +// onNext: 2 +// Info: Finished processing item 3 +// Info: Finished processing item 4 +// onNext: 3 +// onNext: 4 +``` + +## concatMapEagerDelayError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a reactive source, and emits the items that result from concatenating the results of these function applications. A `boolean` value must be specified, which if `true` indicates that all errors from all sources will be delayed until the end, otherwise if `false`, an error from the main source will be signalled when the current source terminates. Unlike [concatMapDelayError](#concatmapdelayerror), this operator eagerly subscribes to all sources. + +### concatMapEagerDelayError example + +```java +Observable source = Observable.create(emitter -> { + emitter.onNext(1); + emitter.onNext(2); + emitter.onError(new Error("Fatal error!")); +}); + +source.doOnError(error -> System.out.println("Info: Error from main source " + error.getMessage())) + .concatMapEagerDelayError(x -> { + return Observable.timer(1, TimeUnit.SECONDS).map(n -> x) + .doOnSubscribe(it -> System.out.println("Info: Processing of item \"" + x + "\" started")); + }, true) + .blockingSubscribe( + x -> System.out.println("onNext: " + x), + error -> System.out.println("onError: " + error.getMessage())); + +// prints: +// Info: Processing of item "1" started +// Info: Processing of item "2" started +// Info: Error from main source Fatal error! +// onNext: 1 +// onNext: 2 +// onError: Fatal error! +``` + +## concatMapIterable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `java.lang.Iterable`, and emits the items that result from concatenating the results of these function applications. + +### concatMapIterable example + +```java +Observable.just("A", "B", "C") + .concatMapIterable(item -> List.of(item, item, item)) + .subscribe(System.out::print); + +// prints AAABBBCCC +``` + +## concatMapMaybe + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.MaybeSource`, and emits the items that result from concatenating these `MaybeSource`s. + +### concatMapMaybe example + +```java +Observable.just("5", "3,14", "2.71", "FF") + .concatMapMaybe(v -> { + return Maybe.fromCallable(() -> Double.parseDouble(v)) + .doOnError(e -> System.out.println("Info: The value \"" + v + "\" could not be parsed.")) + + // Ignore values that can not be parsed. + .onErrorComplete(); + }) + .subscribe(x -> System.out.println("onNext: " + x)); + +// prints: +// onNext: 5.0 +// Info: The value "3,14" could not be parsed. +// onNext: 2.71 +// Info: The value "FF" could not be parsed. +``` + +## concatMapMaybeDelayError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.MaybeSource`, and emits the items that result from concatenating these `MaybeSource`s. Any errors from the sources will be delayed until all of them terminate. + +### concatMapMaybeDelayError example + +```java +DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.uuuu"); +Observable.just("04.03.2018", "12-08-2018", "06.10.2018", "01.12.2018") + .concatMapMaybeDelayError(date -> { + return Maybe.fromCallable(() -> LocalDate.parse(date, dateFormatter)); + }) + .subscribe( + localDate -> System.out.println("onNext: " + localDate), + error -> System.out.println("onError: " + error.getMessage())); + +// prints: +// onNext: 2018-03-04 +// onNext: 2018-10-06 +// onNext: 2018-12-01 +// onError: Text '12-08-2018' could not be parsed at index 2 +``` + +## concatMapSingle + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.SingleSource`, and emits the items that result from concatenating these ``SingleSource`s. + +### concatMapSingle example + +```java +Observable.just("5", "3,14", "2.71", "FF") + .concatMapSingle(v -> { + return Single.fromCallable(() -> Double.parseDouble(v)) + .doOnError(e -> System.out.println("Info: The value \"" + v + "\" could not be parsed.")) + + // Return a default value if the given value can not be parsed. + .onErrorReturnItem(42.0); + }) + .subscribe(x -> System.out.println("onNext: " + x)); + +// prints: +// onNext: 5.0 +// Info: The value "3,14" could not be parsed. +// onNext: 42.0 +// onNext: 2.71 +// Info: The value "FF" could not be parsed. +// onNext: 42.0 +``` + +## concatMapSingleDelayError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.SingleSource`, and emits the items that result from concatenating the results of these function applications. Any errors from the sources will be delayed until all of them terminate. + +### concatMapSingleDelayError example + +```java +DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.uuuu"); +Observable.just("24.03.2018", "12-08-2018", "06.10.2018", "01.12.2018") + .concatMapSingleDelayError(date -> { + return Single.fromCallable(() -> LocalDate.parse(date, dateFormatter)); + }) + .subscribe( + localDate -> System.out.println("onNext: " + localDate), + error -> System.out.println("onError: " + error.getMessage())); + +// prints: +// onNext: 2018-03-24 +// onNext: 2018-10-06 +// onNext: 2018-12-01 +// onError: Text '12-08-2018' could not be parsed at index 2 +``` + +## flatMap + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a reactive source, and emits the items that result from merging the results of these function applications. + +### flatMap example + +```java +Observable.just("A", "B", "C") + .flatMap(a -> { + return Observable.intervalRange(1, 3, 0, 1, TimeUnit.SECONDS) + .map(b -> '(' + a + ", " + b + ')'); + }) + .blockingSubscribe(System.out::println); + +// prints (not necessarily in this order): +// (A, 1) +// (C, 1) +// (B, 1) +// (A, 2) +// (C, 2) +// (B, 2) +// (A, 3) +// (C, 3) +// (B, 3) +``` + +## flatMapCompletable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.CompletableSource`, and returns a `Completable` that completes when all sources completed. + +### flatMapCompletable example + +```java +Observable source = Observable.just(2, 1, 3); +Completable completable = source.flatMapCompletable(x -> { + return Completable.timer(x, TimeUnit.SECONDS) + .doOnComplete(() -> System.out.println("Info: Processing of item \"" + x + "\" completed")); +}); + +completable.doOnComplete(() -> System.out.println("Info: Processing of all items completed")) + .blockingAwait(); + +// prints: +// Info: Processing of item "1" completed +// Info: Processing of item "2" completed +// Info: Processing of item "3" completed +// Info: Processing of all items completed +``` + +## flatMapIterable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `java.lang.Iterable`, and emits the elements from these `Iterable`s. + +### flatMapIterable example + +```java +Observable.just(1, 2, 3, 4) + .flatMapIterable(x -> { + switch (x % 4) { + case 1: + return List.of("A"); + case 2: + return List.of("B", "B"); + case 3: + return List.of("C", "C", "C"); + default: + return List.of(); + } + }) + .subscribe(System.out::println); + +// prints: +// A +// B +// B +// C +// C +// C +``` + +## flatMapMaybe + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.MaybeSource`, and emits the items that result from merging these `MaybeSource`s. + +### flatMapMaybe example + +```java +Observable.just(9.0, 16.0, -4.0) + .flatMapMaybe(x -> { + if (x.compareTo(0.0) < 0) return Maybe.empty(); + else return Maybe.just(Math.sqrt(x)); + }) + .subscribe( + System.out::println, + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// 3.0 +// 4.0 +// onComplete +``` + +## flatMapObservable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to the item emitted by a `Maybe` or `Single`, where that function returns an `io.reactivex.ObservableSource`, and returns an `Observable` that emits the items emitted by this `ObservableSource`. + +### flatMapObservable example + +```java +Single source = Single.just("Kirk, Spock, Chekov, Sulu"); +Observable names = source.flatMapObservable(text -> { + return Observable.fromArray(text.split(",")) + .map(String::strip); +}); + +names.subscribe(name -> System.out.println("onNext: " + name)); + +// prints: +// onNext: Kirk +// onNext: Spock +// onNext: Chekov +// onNext: Sulu +``` + +## flatMapPublisher + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to the item emitted by a `Maybe` or `Single`, where that function returns an `org.reactivestreams.Publisher`, and returns a `Flowable` that emits the items emitted by this `Publisher`. + +### flatMapPublisher example + +```java +Single source = Single.just("Kirk, Spock, Chekov, Sulu"); +Flowable names = source.flatMapPublisher(text -> { + return Flowable.fromArray(text.split(",")) + .map(String::strip); +}); + +names.subscribe(name -> System.out.println("onNext: " + name)); + +// prints: +// onNext: Kirk +// onNext: Spock +// onNext: Chekov +// onNext: Sulu +``` + +## flatMapSingle + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.SingleSource`, and emits the items that result from merging these `SingleSource`s. + +### flatMapSingle example + +```java +Observable.just(4, 2, 1, 3) + .flatMapSingle(x -> Single.timer(x, TimeUnit.SECONDS).map(i -> x)) + .blockingSubscribe(System.out::print); + +// prints 1234 +``` + +*Note:* `Maybe::flatMapSingle` returns a `Single` that signals an error notification if the `Maybe` source is empty: + +```java +Maybe emptySource = Maybe.empty(); +Single result = emptySource.flatMapSingle(x -> Single.just(x)); +result.subscribe( + x -> System.out.println("onSuccess will not be printed!"), + error -> System.out.println("onError: Source was empty!")); + +// prints: +// onError: Source was empty! +``` + +Use [`Maybe::flatMapSingleElement`](#flatmapsingleelement) -- which returns a `Maybe` -- if you don't want this behaviour. + +## flatMapSingleElement + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to the item emitted by a `Maybe`, where that function returns a `io.reactivex.SingleSource`, and returns a `Maybe` that either emits the item emitted by this `SingleSource` or completes if the source `Maybe` just completes. + +### flatMapSingleElement example + +```java +Maybe source = Maybe.just(-42); +Maybe result = source.flatMapSingleElement(x -> { + return Single.just(Math.abs(x)); +}); + +result.subscribe(System.out::println); + +// prints 42 +``` + +## flattenAsFlowable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to the item emitted by a `Maybe` or `Single`, where that function returns a `java.lang.Iterable`, and returns a `Flowable` that emits the elements from this `Iterable`. + +### flattenAsFlowable example + +```java +Single source = Single.just(2.0); +Flowable flowable = source.flattenAsFlowable(x -> { + return List.of(x, Math.pow(x, 2), Math.pow(x, 3)); +}); + +flowable.subscribe(x -> System.out.println("onNext: " + x)); + +// prints: +// onNext: 2.0 +// onNext: 4.0 +// onNext: 8.0 +``` + +## flattenAsObservable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to the item emitted by a `Maybe` or `Single`, where that function returns a `java.lang.Iterable`, and returns an `Observable` that emits the elements from this `Iterable`. + +### flattenAsObservable example + +```java +Single source = Single.just(2.0); +Observable observable = source.flattenAsObservable(x -> { + return List.of(x, Math.pow(x, 2), Math.pow(x, 3)); +}); + +observable.subscribe(x -> System.out.println("onNext: " + x)); + +// prints: +// onNext: 2.0 +// onNext: 4.0 +// onNext: 8.0 +``` + +## groupBy + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/groupby.html](http://reactivex.io/documentation/operators/groupby.html) + +Groups the items emitted by a reactive source according to a specified criterion, and emits these grouped items as a `GroupedObservable` or `GroupedFlowable`. + +### groupBy example + +```java +Observable animals = Observable.just( + "Tiger", "Elephant", "Cat", "Chameleon", "Frog", "Fish", "Turtle", "Flamingo"); + +animals.groupBy(animal -> animal.charAt(0), String::toUpperCase) + .concatMapSingle(Observable::toList) + .subscribe(System.out::println); + +// prints: +// [TIGER, TURTLE] +// [ELEPHANT] +// [CAT, CHAMELEON] +// [FROG, FISH, FLAMINGO] +``` + +## map + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/map.html](http://reactivex.io/documentation/operators/map.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source and emits the results of these function applications. + +### map example + +```java +Observable.just(1, 2, 3) + .map(x -> x * x) + .subscribe(System.out::println); + +// prints: +// 1 +// 4 +// 9 +``` + +## scan + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/scan.html](http://reactivex.io/documentation/operators/scan.html) + +Applies the given `io.reactivex.functions.BiFunction` to a seed value and the first item emitted by a reactive source, then feeds the result of that function application along with the second item emitted by the reactive source into the same function, and so on until all items have been emitted by the reactive source, emitting each intermediate result. + +### scan example + +```java +Observable.just(5, 3, 8, 1, 7) + .scan(0, (partialSum, x) -> partialSum + x) + .subscribe(System.out::println); + +// prints: +// 0 +// 5 +// 8 +// 16 +// 17 +// 24 +``` + +## switchMap + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a reactive source, and emits the items emitted by the most recently projected of these reactive sources. + +### switchMap example + +```java +Observable.interval(0, 1, TimeUnit.SECONDS) + .switchMap(x -> { + return Observable.interval(0, 750, TimeUnit.MILLISECONDS) + .map(y -> x); + }) + .takeWhile(x -> x < 3) + .blockingSubscribe(System.out::print); + +// prints 001122 +``` + +## window + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/window.html](http://reactivex.io/documentation/operators/window.html) + +Collects the items emitted by a reactive source into windows, and emits these windows as a `Flowable` or `Observable`. + +### window example + +```java +Observable.range(1, 10) + + // Create windows containing at most 2 items, and skip 3 items before starting a new window. + .window(2, 3) + .flatMapSingle(window -> { + return window.map(String::valueOf) + .reduce(new StringJoiner(", ", "[", "]"), StringJoiner::add); + }) + .subscribe(System.out::println); + +// prints: +// [1, 2] +// [4, 5] +// [7, 8] +// [10] +``` diff --git a/docs/What's-different-in-2.0.md b/docs/What's-different-in-2.0.md index 4dbaea04b1..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). @@ -294,11 +294,9 @@ We followed the naming convention of Java 8 by defining `io.reactivex.functions. In addition, operators requiring a predicate no longer use `Func1` but have a separate, primitive-returning type of `Predicate` (allows better inlining due to no autoboxing). -The `io.reactivex.functions.Functions` utility class offers common function sources and conversions to `Function`. - # 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() { @@ -356,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: @@ -366,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(); @@ -422,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. @@ -438,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. @@ -605,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() { @@ -935,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. @@ -969,4 +967,4 @@ Flowable.just(1, 2, 3) .doFinally(() -> System.out.println("Finally")) .take(2) // cancels the above after 2 elements .subscribe(System.out::println); -``` \ No newline at end of file +``` 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 32db33b963..518d7a1d51 100644 --- a/gradle/javadoc_cleanup.gradle +++ b/gradle/javadoc_cleanup.gradle @@ -11,6 +11,9 @@ 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/gradle/stylesheet.css b/gradle/stylesheet.css index 5e381dce8b..60f1d665bf 100644 --- a/gradle/stylesheet.css +++ b/gradle/stylesheet.css @@ -534,7 +534,6 @@ td.colLast div { padding-top:0px; } - td.colLast a { padding-bottom:3px; } diff --git a/src/jmh/java/io/reactivex/EachTypeFlatMapPerf.java b/src/jmh/java/io/reactivex/EachTypeFlatMapPerf.java index d8793829f1..7d150960c0 100644 --- a/src/jmh/java/io/reactivex/EachTypeFlatMapPerf.java +++ b/src/jmh/java/io/reactivex/EachTypeFlatMapPerf.java @@ -86,10 +86,12 @@ public Single apply(Integer v) { public void bpRange(Blackhole bh) { bpRange.subscribe(new PerfSubscriber(bh)); } + @Benchmark public void bpRangeMapJust(Blackhole bh) { bpRangeMapJust.subscribe(new PerfSubscriber(bh)); } + @Benchmark public void bpRangeMapRange(Blackhole bh) { bpRangeMapRange.subscribe(new PerfSubscriber(bh)); @@ -99,10 +101,12 @@ public void bpRangeMapRange(Blackhole bh) { public void nbpRange(Blackhole bh) { nbpRange.subscribe(new PerfObserver(bh)); } + @Benchmark public void nbpRangeMapJust(Blackhole bh) { nbpRangeMapJust.subscribe(new PerfObserver(bh)); } + @Benchmark public void nbpRangeMapRange(Blackhole bh) { nbpRangeMapRange.subscribe(new PerfObserver(bh)); @@ -112,6 +116,7 @@ public void nbpRangeMapRange(Blackhole bh) { public void singleJust(Blackhole bh) { singleJust.subscribe(new LatchedSingleObserver(bh)); } + @Benchmark public void singleJustMapJust(Blackhole bh) { singleJustMapJust.subscribe(new LatchedSingleObserver(bh)); diff --git a/src/jmh/java/io/reactivex/LatchedSingleObserver.java b/src/jmh/java/io/reactivex/LatchedSingleObserver.java index 02e74c463e..59b980a0eb 100644 --- a/src/jmh/java/io/reactivex/LatchedSingleObserver.java +++ b/src/jmh/java/io/reactivex/LatchedSingleObserver.java @@ -26,15 +26,18 @@ public LatchedSingleObserver(Blackhole bh) { this.bh = bh; this.cdl = new CountDownLatch(1); } + @Override public void onSubscribe(Disposable d) { } + @Override public void onSuccess(T value) { bh.consume(value); cdl.countDown(); } + @Override public void onError(Throwable e) { e.printStackTrace(); diff --git a/src/jmh/java/io/reactivex/PerfObserver.java b/src/jmh/java/io/reactivex/PerfObserver.java index 33548155ba..8de241e6a0 100644 --- a/src/jmh/java/io/reactivex/PerfObserver.java +++ b/src/jmh/java/io/reactivex/PerfObserver.java @@ -26,19 +26,23 @@ public PerfObserver(Blackhole bh) { this.bh = bh; this.cdl = new CountDownLatch(1); } + @Override public void onSubscribe(Disposable d) { } + @Override public void onNext(Object value) { bh.consume(value); } + @Override public void onError(Throwable e) { e.printStackTrace(); cdl.countDown(); } + @Override public void onComplete() { cdl.countDown(); diff --git a/src/jmh/java/io/reactivex/PublishProcessorPerf.java b/src/jmh/java/io/reactivex/PublishProcessorPerf.java index 37afebfa4f..d9531ab267 100644 --- a/src/jmh/java/io/reactivex/PublishProcessorPerf.java +++ b/src/jmh/java/io/reactivex/PublishProcessorPerf.java @@ -71,7 +71,6 @@ public void bounded1() { bounded.onNext(1); } - @Benchmark public void bounded1k() { for (int i = 0; i < 1000; i++) { @@ -86,13 +85,11 @@ public void bounded1m() { } } - @Benchmark public void subject1() { subject.onNext(1); } - @Benchmark public void subject1k() { for (int i = 0; i < 1000; i++) { diff --git a/src/jmh/java/io/reactivex/ToFlowablePerf.java b/src/jmh/java/io/reactivex/ToFlowablePerf.java index 9bc41083f4..deb97a7e51 100644 --- a/src/jmh/java/io/reactivex/ToFlowablePerf.java +++ b/src/jmh/java/io/reactivex/ToFlowablePerf.java @@ -78,6 +78,7 @@ public Observable apply(Integer v) throws Exception { public Object flowable() { return flowable.blockingGet(); } + @Benchmark public Object flowableInner() { return flowableInner.blockingLast(); diff --git a/src/jmh/java/io/reactivex/XMapYPerf.java b/src/jmh/java/io/reactivex/XMapYPerf.java index 389a89000f..d9e198b284 100644 --- a/src/jmh/java/io/reactivex/XMapYPerf.java +++ b/src/jmh/java/io/reactivex/XMapYPerf.java @@ -94,7 +94,6 @@ public class XMapYPerf { Observable obsFlatMapIterableAsObs0; - @Setup public void setup() { Integer[] values = new Integer[times]; @@ -158,7 +157,6 @@ public Iterable apply(Integer v) throws Exception { } }); - flowFlatMapSingle1 = fsource.flatMapSingle(new Function>() { @Override public SingleSource apply(Integer v) throws Exception { @@ -231,7 +229,6 @@ public Publisher apply(Integer v) throws Exception { } }); - // ------------------------------------------------------------------- Observable osource = Observable.fromArray(values); @@ -271,7 +268,6 @@ public MaybeSource apply(Integer v) throws Exception { } }); - obsFlatMapCompletable0 = osource.flatMapCompletable(new Function() { @Override public CompletableSource apply(Integer v) throws Exception { diff --git a/src/jmh/java/io/reactivex/parallel/ParallelPerf.java b/src/jmh/java/io/reactivex/parallel/ParallelPerf.java index 1630e82eef..24a0e85954 100644 --- a/src/jmh/java/io/reactivex/parallel/ParallelPerf.java +++ b/src/jmh/java/io/reactivex/parallel/ParallelPerf.java @@ -28,7 +28,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 5) @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) -@Fork(value = 1,jvmArgsAppend = { "-XX:MaxInlineLevel=20" }) +@Fork(value = 1, jvmArgsAppend = { "-XX:MaxInlineLevel=20" }) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class ParallelPerf implements Function { diff --git a/src/main/java/io/reactivex/Completable.java b/src/main/java/io/reactivex/Completable.java index b4f18a56c2..1994632307 100644 --- a/src/main/java/io/reactivex/Completable.java +++ b/src/main/java/io/reactivex/Completable.java @@ -26,7 +26,7 @@ import io.reactivex.internal.operators.completable.*; import io.reactivex.internal.operators.maybe.*; import io.reactivex.internal.operators.mixed.*; -import io.reactivex.internal.operators.single.SingleDelayWithCompletable; +import io.reactivex.internal.operators.single.*; import io.reactivex.internal.util.ExceptionHelper; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; @@ -90,7 +90,7 @@ * d.dispose(); * *

- * Note that by design, subscriptions via {@link #subscribe(CompletableObserver)} can't be cancelled/disposed + * Note that by design, subscriptions via {@link #subscribe(CompletableObserver)} can't be disposed * from the outside (hence the * {@code void} return of the {@link #subscribe(CompletableObserver)} method) and it is the * responsibility of the implementor of the {@code CompletableObserver} to allow this to happen. @@ -105,7 +105,7 @@ public abstract class Completable implements CompletableSource { /** * Returns a Completable which terminates as soon as one of the source Completables - * terminates (normally or with an error) and cancels all other Completables. + * terminates (normally or with an error) and disposes all other Completables. *

* *

@@ -118,6 +118,7 @@ public abstract class Completable implements CompletableSource { * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable ambArray(final CompletableSource... sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -133,7 +134,7 @@ public static Completable ambArray(final CompletableSource... sources) { /** * Returns a Completable which terminates as soon as one of the source Completables - * terminates (normally or with an error) and cancels all other Completables. + * terminates (normally or with an error) and disposes all other Completables. *

* *

@@ -146,6 +147,7 @@ public static Completable ambArray(final CompletableSource... sources) { * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable amb(final Iterable sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -164,6 +166,7 @@ public static Completable amb(final Iterable source * @return a Completable instance that completes immediately */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable complete() { return RxJavaPlugins.onAssembly(CompletableEmpty.INSTANCE); @@ -182,6 +185,7 @@ public static Completable complete() { * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable concatArray(CompletableSource... sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -207,6 +211,7 @@ public static Completable concatArray(CompletableSource... sources) { * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable concat(Iterable sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -253,6 +258,7 @@ public static Completable concat(Publisher sources) * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @BackpressureSupport(BackpressureKind.FULL) public static Completable concat(Publisher sources, int prefetch) { @@ -297,6 +303,7 @@ public static Completable concat(Publisher sources, * @see Cancellable */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable create(CompletableOnSubscribe source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -306,7 +313,7 @@ public static Completable create(CompletableOnSubscribe source) { /** * Constructs a Completable instance by wrapping the given source callback * without any safeguards; you should manage the lifecycle and response - * to downstream cancellation/dispose. + * to downstream disposal. *

* *

@@ -319,6 +326,7 @@ public static Completable create(CompletableOnSubscribe source) { * @throws NullPointerException if source is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable unsafeCreate(CompletableSource source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -340,6 +348,7 @@ public static Completable unsafeCreate(CompletableSource source) { * @return the Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable defer(final Callable completableSupplier) { ObjectHelper.requireNonNull(completableSupplier, "completableSupplier"); @@ -363,6 +372,7 @@ public static Completable defer(final Callable comp * @throws NullPointerException if errorSupplier is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable error(final Callable errorSupplier) { ObjectHelper.requireNonNull(errorSupplier, "errorSupplier is null"); @@ -382,13 +392,13 @@ public static Completable error(final Callable errorSupplie * @throws NullPointerException if error is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable error(final Throwable error) { ObjectHelper.requireNonNull(error, "error is null"); return RxJavaPlugins.onAssembly(new CompletableError(error)); } - /** * Returns a Completable instance that runs the given Action for each subscriber and * emits either an unchecked exception or simply completes. @@ -397,12 +407,20 @@ public static Completable error(final Throwable error) { *
*
Scheduler:
*
{@code fromAction} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the {@link Action} 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 * @throws NullPointerException if run is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable fromAction(final Action run) { ObjectHelper.requireNonNull(run, "run is null"); @@ -417,11 +435,19 @@ public static Completable fromAction(final Action run) { *
*
Scheduler:
*
{@code fromCallable} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the {@link Callable} 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 callable the callable instance to execute for each subscriber * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable fromCallable(final Callable callable) { ObjectHelper.requireNonNull(callable, "callable is null"); @@ -433,7 +459,7 @@ public static Completable fromCallable(final Callable callable) { *

* *

- * Note that cancellation from any of the subscribers to this Completable will cancel the future. + * Note that if any of the observers to this Completable call dispose, this Completable will cancel the future. *

*
Scheduler:
*
{@code fromFuture} does not operate by default on a particular {@link Scheduler}.
@@ -442,6 +468,7 @@ public static Completable fromCallable(final Callable callable) { * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable fromFuture(final Future future) { ObjectHelper.requireNonNull(future, "future is null"); @@ -466,6 +493,7 @@ public static Completable fromFuture(final Future future) { * @since 2.2 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable fromMaybe(final MaybeSource maybe) { ObjectHelper.requireNonNull(maybe, "maybe is null"); @@ -480,12 +508,20 @@ 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 * @throws NullPointerException if run is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable fromRunnable(final Runnable run) { ObjectHelper.requireNonNull(run, "run is null"); @@ -507,6 +543,7 @@ public static Completable fromRunnable(final Runnable run) { * @throws NullPointerException if flowable is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable fromObservable(final ObservableSource observable) { ObjectHelper.requireNonNull(observable, "observable is null"); @@ -520,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 @@ -543,6 +580,7 @@ public static Completable fromObservable(final ObservableSource observabl * @see #create(CompletableOnSubscribe) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public static Completable fromPublisher(final Publisher publisher) { @@ -565,6 +603,7 @@ public static Completable fromPublisher(final Publisher publisher) { * @throws NullPointerException if single is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable fromSingle(final SingleSource single) { ObjectHelper.requireNonNull(single, "single is null"); @@ -581,13 +620,13 @@ public static Completable fromSingle(final SingleSource single) { *

{@code mergeArray} does not operate by default on a particular {@link Scheduler}.
*
Error handling:
*
If any of the source {@code CompletableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are cancelled. + * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are disposed. * If more than one {@code CompletableSource} signals an error, the resulting {@code Completable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Completable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Completable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeArrayDelayError(CompletableSource...)} to merge sources and terminate only when all source {@code CompletableSource}s * have completed or failed with an error. @@ -599,6 +638,7 @@ public static Completable fromSingle(final SingleSource single) { * @see #mergeArrayDelayError(CompletableSource...) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable mergeArray(CompletableSource... sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -621,13 +661,13 @@ public static Completable mergeArray(CompletableSource... sources) { *
{@code merge} does not operate by default on a particular {@link Scheduler}.
*
Error handling:
*
If any of the source {@code CompletableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are cancelled. + * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are disposed. * If more than one {@code CompletableSource} signals an error, the resulting {@code Completable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Completable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Completable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeDelayError(Iterable)} to merge sources and terminate only when all source {@code CompletableSource}s * have completed or failed with an error. @@ -639,6 +679,7 @@ public static Completable mergeArray(CompletableSource... sources) { * @see #mergeDelayError(Iterable) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable merge(Iterable sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -658,13 +699,13 @@ public static Completable merge(Iterable sources) { *
{@code merge} does not operate by default on a particular {@link Scheduler}.
*
Error handling:
*
If any of the source {@code CompletableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are cancelled. + * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are disposed. * If more than one {@code CompletableSource} signals an error, the resulting {@code Completable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Completable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Completable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeDelayError(Publisher)} to merge sources and terminate only when all source {@code CompletableSource}s * have completed or failed with an error. @@ -695,13 +736,13 @@ public static Completable merge(Publisher sources) *
{@code merge} does not operate by default on a particular {@link Scheduler}.
*
Error handling:
*
If any of the source {@code CompletableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are cancelled. + * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are disposed. * If more than one {@code CompletableSource} signals an error, the resulting {@code Completable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Completable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Completable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeDelayError(Publisher, int)} to merge sources and terminate only when all source {@code CompletableSource}s * have completed or failed with an error. @@ -740,6 +781,7 @@ public static Completable merge(Publisher sources, * @throws IllegalArgumentException if maxConcurrency is less than 1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @BackpressureSupport(BackpressureKind.FULL) private static Completable merge0(Publisher sources, int maxConcurrency, boolean delayErrors) { @@ -763,6 +805,7 @@ private static Completable merge0(Publisher sources * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable mergeArrayDelayError(CompletableSource... sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -784,13 +827,13 @@ public static Completable mergeArrayDelayError(CompletableSource... sources) { * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable mergeDelayError(Iterable sources) { ObjectHelper.requireNonNull(sources, "sources is null"); return RxJavaPlugins.onAssembly(new CompletableMergeDelayErrorIterable(sources)); } - /** * Returns a Completable that subscribes to all Completables in the source sequence and delays * any error emitted by either the sources observable or any of the inner Completables until all of @@ -890,6 +933,7 @@ public static Completable timer(long delay, TimeUnit unit) { * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static Completable timer(final long delay, final TimeUnit unit, final Scheduler scheduler) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -940,7 +984,7 @@ public static Completable using(Callable resourceSupplier, *

* *

- * If this overload performs a lazy cancellation after the terminal event is emitted. + * If this overload performs a lazy disposal after the terminal event is emitted. * Exceptions thrown at this time will be delivered to RxJavaPlugins only. *

*
Scheduler:
@@ -956,6 +1000,7 @@ public static Completable using(Callable resourceSupplier, * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable using( final Callable resourceSupplier, @@ -983,6 +1028,7 @@ public static Completable using( * @throws NullPointerException if source is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable wrap(CompletableSource source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -1007,6 +1053,7 @@ public static Completable wrap(CompletableSource source) { * @throws NullPointerException if other is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable ambWith(CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -1030,6 +1077,7 @@ public final Completable ambWith(CompletableSource other) { * @throws NullPointerException if next is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Observable andThen(ObservableSource next) { ObjectHelper.requireNonNull(next, "next is null"); @@ -1056,6 +1104,7 @@ public final Observable andThen(ObservableSource next) { * @throws NullPointerException if next is null */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable andThen(Publisher next) { @@ -1080,6 +1129,7 @@ public final Flowable andThen(Publisher next) { * @return Single that composes this Completable and next */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single andThen(SingleSource next) { ObjectHelper.requireNonNull(next, "next is null"); @@ -1103,6 +1153,7 @@ public final Single andThen(SingleSource next) { * @return Maybe that composes this Completable and next */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe andThen(MaybeSource next) { ObjectHelper.requireNonNull(next, "next is null"); @@ -1127,7 +1178,8 @@ public final Maybe andThen(MaybeSource next) { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Completable andThen(CompletableSource next) { - return concatWith(next); + ObjectHelper.requireNonNull(next, "next is null"); + return RxJavaPlugins.onAssembly(new CompletableAndThenCompletable(this, next)); } /** @@ -1195,6 +1247,7 @@ public final void blockingAwait() { * @throws RuntimeException wrapping an InterruptedException if the current thread is interrupted */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final boolean blockingAwait(long timeout, TimeUnit unit) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -1215,6 +1268,7 @@ public final boolean blockingAwait(long timeout, TimeUnit unit) { * @return the throwable if this terminated with an error, null otherwise * @throws RuntimeException that wraps an InterruptedException if the wait is interrupted */ + @Nullable @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Throwable blockingGet() { @@ -1238,6 +1292,7 @@ public final Throwable blockingGet() { * @throws RuntimeException that wraps an InterruptedException if the wait is interrupted or * TimeoutException if the specified timeout elapsed before it */ + @Nullable @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Throwable blockingGet(long timeout, TimeUnit unit) { @@ -1306,10 +1361,11 @@ public final Completable compose(CompletableTransformer transformer) { * @see #andThen(Publisher) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable concatWith(CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); - return concatArray(this, other); + return RxJavaPlugins.onAssembly(new CompletableAndThenCompletable(this, other)); } /** @@ -1369,6 +1425,7 @@ public final Completable delay(long delay, TimeUnit unit, Scheduler scheduler) { * @throws NullPointerException if unit or scheduler is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Completable delay(final long delay, final TimeUnit unit, final Scheduler scheduler, final boolean delayError) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -1376,6 +1433,53 @@ public final Completable delay(final long delay, final TimeUnit unit, final Sche return RxJavaPlugins.onAssembly(new CompletableDelay(this, delay, unit, scheduler, delayError)); } + /** + * Returns a Completable that delays the subscription to the source CompletableSource by a given amount of time. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code delaySubscription} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay the time to delay the subscription + * @param unit the time unit of {@code delay} + * @return a Completable that delays the subscription to the source CompletableSource by the given amount + * @since 2.2.3 - experimental + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @Experimental + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Completable delaySubscription(long delay, TimeUnit unit) { + return delaySubscription(delay, unit, Schedulers.computation()); + } + + /** + * Returns a Completable that delays the subscription to the source CompletableSource by a given amount of time, + * both waiting and subscribing on a given Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay the time to delay the subscription + * @param unit the time unit of {@code delay} + * @param scheduler the Scheduler on which the waiting and subscription will happen + * @return a Completable that delays the subscription to the source CompletableSource by a given + * amount, waiting and subscribing on the given Scheduler + * @since 2.2.3 - experimental + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @Experimental + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Completable delaySubscription(long delay, TimeUnit unit, Scheduler scheduler) { + return Completable.timer(delay, unit, scheduler).andThen(this); + } + /** * Returns a Completable which calls the given onComplete callback if this Completable completes. *

@@ -1453,6 +1557,7 @@ public final Completable doOnError(Consumer onError) { * @throws NullPointerException if onEvent is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable doOnEvent(final Consumer onEvent) { ObjectHelper.requireNonNull(onEvent, "onEvent is null"); @@ -1474,6 +1579,7 @@ public final Completable doOnEvent(final Consumer onEvent) { * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) private Completable doOnLifecycle( final Consumer onSubscribe, @@ -1573,11 +1679,12 @@ public final Completable doAfterTerminate(final Action onAfterTerminate) { *

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

History: 2.0.1 - experimental - * @param onFinally the action called when this Completable terminates or gets cancelled + * @param onFinally the action called when this Completable terminates or gets disposed * @return the new Completable instance * @since 2.1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable doFinally(Action onFinally) { ObjectHelper.requireNonNull(onFinally, "onFinally is null"); @@ -1715,12 +1822,34 @@ public final Completable doFinally(Action onFinally) { * @see #compose(CompletableTransformer) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable lift(final CompletableOperator onLift) { ObjectHelper.requireNonNull(onLift, "onLift is null"); return RxJavaPlugins.onAssembly(new CompletableLift(this, onLift)); } + /** + * Maps the signal types of this Completable into a {@link Notification} of the same kind + * and emits it as a single success value to downstream. + *

+ * + *

+ *
Scheduler:
+ *
{@code materialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the intended target element type of the notification + * @return the new Single instance + * @since 2.2.4 - experimental + * @see Single#dematerialize(Function) + */ + @Experimental + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> materialize() { + return RxJavaPlugins.onAssembly(new CompletableMaterialize(this)); + } + /** * Returns a Completable which subscribes to this and the other Completable and completes * when both of them complete or one emits an error. @@ -1735,6 +1864,7 @@ public final Completable lift(final CompletableOperator onLift) { * @throws NullPointerException if other is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable mergeWith(CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -1754,6 +1884,7 @@ public final Completable mergeWith(CompletableSource other) { * @throws NullPointerException if scheduler is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Completable observeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -1791,6 +1922,7 @@ public final Completable onErrorComplete() { * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable onErrorComplete(final Predicate predicate) { ObjectHelper.requireNonNull(predicate, "predicate is null"); @@ -1813,6 +1945,7 @@ public final Completable onErrorComplete(final Predicate pred * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable onErrorResumeNext(final Function errorMapper) { ObjectHelper.requireNonNull(errorMapper, "errorMapper is null"); @@ -1840,7 +1973,7 @@ public final Completable onTerminateDetach() { } /** - * Returns a Completable that repeatedly subscribes to this Completable until cancelled. + * Returns a Completable that repeatedly subscribes to this Completable until disposed. *

* *

@@ -1957,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 */ @@ -1977,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 @@ -2071,6 +2204,7 @@ public final Completable retryWhen(Function, ? exten * @throws NullPointerException if other is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable startWith(CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2092,6 +2226,7 @@ public final Completable startWith(CompletableSource other) { * @throws NullPointerException if other is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Observable startWith(Observable other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2115,6 +2250,7 @@ public final Observable startWith(Observable other) { * @throws NullPointerException if other is null */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable startWith(Publisher other) { @@ -2143,7 +2279,7 @@ public final Completable hide() { } /** - * Subscribes to this CompletableConsumable and returns a Disposable which can be used to cancel + * Subscribes to this CompletableConsumable and returns a Disposable which can be used to dispose * the subscription. *

* @@ -2151,7 +2287,7 @@ public final Completable hide() { *

Scheduler:
*
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
*
- * @return the Disposable that allows cancelling the subscription + * @return the Disposable that allows disposing the subscription */ @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe() { @@ -2163,11 +2299,13 @@ 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); + ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null CompletableObserver. Please check the handler provided to RxJavaPlugins.setOnCompletableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); + subscribeActual(observer); } catch (NullPointerException ex) { // NOPMD throw ex; @@ -2231,10 +2369,11 @@ public final E subscribeWith(E observer) { *
* @param onComplete the runnable that is called if the Completable completes normally * @param onError the consumer that is called if this Completable emits an error - * @return the Disposable that can be used for cancelling the subscription asynchronously + * @return the Disposable that can be used for disposing the subscription asynchronously * @throws NullPointerException if either callback is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe(final Action onComplete, final Consumer onError) { ObjectHelper.requireNonNull(onError, "onError is null"); @@ -2259,9 +2398,10 @@ public final Disposable subscribe(final Action onComplete, final Consumer{@code subscribe} does not operate by default on a particular {@link Scheduler}. *
* @param onComplete the runnable called when this Completable completes normally - * @return the Disposable that allows cancelling the subscription + * @return the Disposable that allows disposing the subscription */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe(final Action onComplete) { ObjectHelper.requireNonNull(onComplete, "onComplete is null"); @@ -2285,6 +2425,7 @@ public final Disposable subscribe(final Action onComplete) { * @throws NullPointerException if scheduler is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Completable subscribeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -2311,6 +2452,7 @@ public final Completable subscribeOn(final Scheduler scheduler) { * @since 2.2 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable takeUntil(CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2355,6 +2497,7 @@ public final Completable timeout(long timeout, TimeUnit unit) { * @throws NullPointerException if unit or other is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.COMPUTATION) public final Completable timeout(long timeout, TimeUnit unit, CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2402,6 +2545,7 @@ public final Completable timeout(long timeout, TimeUnit unit, Scheduler schedule * @throws NullPointerException if unit, scheduler or other is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Completable timeout(long timeout, TimeUnit unit, Scheduler scheduler, CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2425,6 +2569,7 @@ public final Completable timeout(long timeout, TimeUnit unit, Scheduler schedule * @throws NullPointerException if unit or scheduler */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) private Completable timeout0(long timeout, TimeUnit unit, Scheduler scheduler, CompletableSource other) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -2541,6 +2686,7 @@ public final Observable toObservable() { * @throws NullPointerException if completionValueSupplier is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single toSingle(final Callable completionValueSupplier) { ObjectHelper.requireNonNull(completionValueSupplier, "completionValueSupplier is null"); @@ -2562,6 +2708,7 @@ public final Single toSingle(final Callable completionValueS * @throws NullPointerException if completionValue is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single toSingleDefault(final T completionValue) { ObjectHelper.requireNonNull(completionValue, "completionValue is null"); @@ -2569,7 +2716,7 @@ public final Single toSingleDefault(final T completionValue) { } /** - * Returns a Completable which makes sure when a subscriber cancels the subscription, the + * Returns a Completable which makes sure when a subscriber disposes the subscription, the * dispose is called on the specified scheduler. *

* @@ -2577,11 +2724,12 @@ public final Single toSingleDefault(final T completionValue) { *

Scheduler:
*
{@code unsubscribeOn} calls dispose() of the upstream on the {@link Scheduler} you specify.
* - * @param scheduler the target scheduler where to execute the cancellation + * @param scheduler the target scheduler where to execute the disposing * @return the new Completable instance * @throws NullPointerException if scheduler is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Completable unsubscribeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); diff --git a/src/main/java/io/reactivex/Emitter.java b/src/main/java/io/reactivex/Emitter.java index 2f60f90478..0d95e80dcf 100644 --- a/src/main/java/io/reactivex/Emitter.java +++ b/src/main/java/io/reactivex/Emitter.java @@ -17,6 +17,11 @@ /** * Base interface for emitting signals in a push-fashion in various generator-like source * operators (create, generate). + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently. Calling them from multiple threads is not supported and leads to an + * undefined behavior. * * @param the value type emitted */ diff --git a/src/main/java/io/reactivex/Flowable.java b/src/main/java/io/reactivex/Flowable.java index 6af68db61d..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; @@ -27,7 +26,7 @@ import io.reactivex.internal.fuseable.*; import io.reactivex.internal.operators.flowable.*; import io.reactivex.internal.operators.mixed.*; -import io.reactivex.internal.operators.observable.ObservableFromPublisher; +import io.reactivex.internal.operators.observable.*; import io.reactivex.internal.schedulers.ImmediateThinScheduler; import io.reactivex.internal.subscribers.*; import io.reactivex.internal.util.*; @@ -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. */ @@ -87,6 +178,7 @@ public abstract class Flowable implements Publisher { * @see ReactiveX operators documentation: Amb */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable amb(Iterable> sources) { @@ -116,6 +208,7 @@ public static Flowable amb(Iterable> sou * @see ReactiveX operators documentation: Amb */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable ambArray(Publisher... sources) { @@ -269,6 +362,7 @@ public static Flowable combineLatest(Function Flowable combineLatest(Publisher[] sources, Function combiner, int bufferSize) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -366,6 +460,7 @@ public static Flowable combineLatest(Iterable Flowable combineLatest(Iterable> sources, Function combiner, int bufferSize) { @@ -556,6 +651,7 @@ public static Flowable combineLatestDelayError(Function Flowable combineLatestDelayError(Publisher[] sources, Function combiner, int bufferSize) { @@ -747,6 +843,7 @@ public static Flowable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable combineLatest( @@ -799,6 +896,7 @@ public static Flowable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable combineLatest( @@ -855,6 +953,7 @@ public static Flowable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable combineLatest( @@ -916,6 +1015,7 @@ public static Flowable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable combineLatest( @@ -981,6 +1081,7 @@ public static Flowable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable combineLatest( @@ -1051,6 +1152,7 @@ public static Flowable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable combineLatest( @@ -1125,6 +1227,7 @@ public static Flowable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable combineLatest( @@ -1166,6 +1269,7 @@ public static Flowable combineLatest( */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable concat(Iterable> sources) { @@ -1261,6 +1365,7 @@ public static Flowable concat(Publisher> */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable concat(Publisher source1, Publisher source2) { @@ -1297,6 +1402,7 @@ public static Flowable concat(Publisher source1, Publisher Flowable concat( @@ -1338,6 +1444,7 @@ public static Flowable concat( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable concat( @@ -1470,6 +1577,7 @@ public static Flowable concatArrayEager(Publisher... sources * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -1564,6 +1672,7 @@ public static Flowable concatArrayEagerDelayError(int maxConcurrency, int */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable concatDelayError(Iterable> sources) { @@ -1668,6 +1777,7 @@ public static Flowable concatEager(Publisher Flowable concatEager(Iterable Flowable concatEager(Iterable Flowable create(FlowableOnSubscribe source, BackpressureStrategy mode) { @@ -1820,6 +1932,7 @@ public static Flowable create(FlowableOnSubscribe source, Backpressure * @see ReactiveX operators documentation: Defer */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable defer(Callable> supplier) { @@ -1874,10 +1987,11 @@ public static Flowable empty() { * @see ReactiveX operators documentation: Throw */ @CheckReturnValue + @NonNull @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)); } @@ -1902,6 +2016,7 @@ public static Flowable error(Callable supplier) { * @see ReactiveX operators documentation: Throw */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable error(final Throwable throwable) { @@ -1929,6 +2044,7 @@ public static Flowable error(final Throwable throwable) { * @see ReactiveX operators documentation: From */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable fromArray(T... items) { @@ -1955,6 +2071,13 @@ public static Flowable fromArray(T... items) { *

The operator honors backpressure from downstream.
*
Scheduler:
*
{@code fromCallable} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the {@link Callable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link Subscriber#onError(Throwable)}, + * except when the downstream has canceled this {@code Flowable} 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 supplier @@ -1967,6 +2090,7 @@ public static Flowable fromArray(T... items) { * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable fromCallable(Callable supplier) { @@ -2003,6 +2127,7 @@ public static Flowable fromCallable(Callable supplier) { * @see ReactiveX operators documentation: From */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable fromFuture(Future future) { @@ -2043,6 +2168,7 @@ public static Flowable fromFuture(Future future) { * @see ReactiveX operators documentation: From */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable fromFuture(Future future, long timeout, TimeUnit unit) { @@ -2088,6 +2214,7 @@ public static Flowable fromFuture(Future future, long timeou */ @SuppressWarnings({ "unchecked", "cast" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public static Flowable fromFuture(Future future, long timeout, TimeUnit unit, Scheduler scheduler) { @@ -2126,6 +2253,7 @@ public static Flowable fromFuture(Future future, long timeou */ @SuppressWarnings({ "cast", "unchecked" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public static Flowable fromFuture(Future future, Scheduler scheduler) { @@ -2154,6 +2282,7 @@ public static Flowable fromFuture(Future future, Scheduler s * @see ReactiveX operators documentation: From */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable fromIterable(Iterable source) { @@ -2162,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 @@ -2189,6 +2318,7 @@ public static Flowable fromIterable(Iterable source) { * @see #create(FlowableOnSubscribe, BackpressureStrategy) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -2196,13 +2326,18 @@ 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)); } /** * Returns a cold, synchronous, stateless and backpressure-aware generator of values. + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. *

*
Backpressure:
*
The operator honors downstream backpressure.
@@ -2218,6 +2353,7 @@ public static Flowable fromPublisher(final Publisher source) * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable generate(final Consumer> generator) { @@ -2229,6 +2365,11 @@ public static Flowable generate(final Consumer> generator) { /** * Returns a cold, synchronous, stateful and backpressure-aware generator of values. + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. *

*
Backpressure:
*
The operator honors downstream backpressure.
@@ -2246,6 +2387,7 @@ public static Flowable generate(final Consumer> generator) { * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable generate(Callable initialState, final BiConsumer> generator) { @@ -2256,6 +2398,11 @@ public static Flowable generate(Callable initialState, final BiCons /** * Returns a cold, synchronous, stateful and backpressure-aware generator of values. + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. *

*
Backpressure:
*
The operator honors downstream backpressure.
@@ -2275,6 +2422,7 @@ public static Flowable generate(Callable initialState, final BiCons * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable generate(Callable initialState, final BiConsumer> generator, @@ -2285,6 +2433,11 @@ public static Flowable generate(Callable initialState, final BiCons /** * Returns a cold, synchronous, stateful and backpressure-aware generator of values. + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. *

*
Backpressure:
*
The operator honors downstream backpressure.
@@ -2311,6 +2464,11 @@ public static Flowable generate(Callable initialState, BiFunction + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. *
*
Backpressure:
*
The operator honors downstream backpressure.
@@ -2331,6 +2489,7 @@ public static Flowable generate(Callable initialState, BiFunction Flowable generate(Callable initialState, BiFunction, S> generator, Consumer disposeState) { @@ -2400,6 +2559,7 @@ public static Flowable interval(long initialDelay, long period, TimeUnit u * @since 1.0.12 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public static Flowable interval(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { @@ -2507,6 +2667,7 @@ public static Flowable intervalRange(long start, long count, long initialD * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public static Flowable intervalRange(long start, long count, long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { @@ -2558,6 +2719,7 @@ public static Flowable intervalRange(long start, long count, long initialD * @see #fromIterable(Iterable) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable just(T item) { @@ -2587,11 +2749,12 @@ public static Flowable just(T item) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @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); } @@ -2620,12 +2783,13 @@ public static Flowable just(T item1, T item2) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @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); } @@ -2656,13 +2820,14 @@ public static Flowable just(T item1, T item2, T item3) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @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); } @@ -2695,14 +2860,15 @@ public static Flowable just(T item1, T item2, T item3, T item4) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @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); } @@ -2737,15 +2903,16 @@ public static Flowable just(T item1, T item2, T item3, T item4, T item5) */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @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); } @@ -2782,16 +2949,17 @@ public static Flowable just(T item1, T item2, T item3, T item4, T item5, */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @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); } @@ -2830,17 +2998,18 @@ public static Flowable just(T item1, T item2, T item3, T item4, T item5, */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @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); } @@ -2881,18 +3050,19 @@ public static Flowable just(T item1, T item2, T item3, T item4, T item5, */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @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); } @@ -2935,19 +3105,20 @@ public static Flowable just(T item1, T item2, T item3, T item4, T item5, */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @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); } @@ -3325,6 +3496,7 @@ public static Flowable mergeArray(Publisher... sources) { */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable merge(Publisher source1, Publisher source2) { @@ -3374,6 +3546,7 @@ public static Flowable merge(Publisher source1, Publisher Flowable merge(Publisher source1, Publisher source2, Publisher source3) { @@ -3426,6 +3599,7 @@ public static Flowable merge(Publisher source1, Publisher Flowable merge( @@ -3474,7 +3648,6 @@ public static Flowable mergeDelayError(Iterable Flowable mergeArrayDelayError(Publisher... sou */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable mergeDelayError(Publisher source1, Publisher source2) { @@ -3778,6 +3952,7 @@ public static Flowable mergeDelayError(Publisher source1, Pu */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable mergeDelayError(Publisher source1, Publisher source2, Publisher source3) { @@ -3787,7 +3962,6 @@ public static Flowable mergeDelayError(Publisher source1, Pu return fromArray(source1, source2, source3).flatMap((Function)Functions.identity(), true, 3); } - /** * Flattens four Publishers into one Publisher, in a way that allows a Subscriber to receive all * successfully emitted items from all of the source Publishers without being interrupted by an error @@ -3824,6 +3998,7 @@ public static Flowable mergeDelayError(Publisher source1, Pu */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable mergeDelayError( @@ -4039,6 +4214,7 @@ public static Single sequenceEqual(Publisher source1, * @see ReactiveX operators documentation: SequenceEqual */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Single sequenceEqual(Publisher source1, Publisher source2, @@ -4289,6 +4465,7 @@ public static Flowable timer(long delay, TimeUnit unit) { * @see ReactiveX operators documentation: Timer */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public static Flowable timer(long delay, TimeUnit unit, Scheduler scheduler) { @@ -4300,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:
@@ -4317,6 +4494,7 @@ public static Flowable timer(long delay, TimeUnit unit, Scheduler schedule * instead. */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.NONE) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable unsafeCreate(Publisher onSubscribe) { @@ -4390,6 +4568,7 @@ public static Flowable using(Callable resourceSupplier, * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable using(Callable resourceSupplier, @@ -4397,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)); } @@ -4446,6 +4625,7 @@ public static Flowable using(Callable resourceSupplier, * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zip(Iterable> sources, Function zipper) { @@ -4500,6 +4680,7 @@ public static Flowable zip(Iterable> */ @SuppressWarnings({ "rawtypes", "unchecked", "cast" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zip(Publisher> sources, @@ -4558,6 +4739,7 @@ public static Flowable zip(Publisher> */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zip( @@ -4619,6 +4801,7 @@ public static Flowable zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zip( @@ -4629,7 +4812,6 @@ public static Flowable zip( return zipArray(Functions.toFunction(zipper), delayError, bufferSize(), source1, source2); } - /** * Returns a Flowable that emits the results of a specified combiner function applied to combinations of * two items emitted, in sequence, by two other Publishers. @@ -4682,6 +4864,7 @@ public static Flowable zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zip( @@ -4746,6 +4929,7 @@ public static Flowable zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zip( @@ -4814,6 +4998,7 @@ public static Flowable zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zip( @@ -4887,6 +5072,7 @@ public static Flowable zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zip( @@ -4963,6 +5149,7 @@ public static Flowable zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zip( @@ -5043,6 +5230,7 @@ public static Flowable zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zip( @@ -5128,6 +5316,7 @@ public static Flowable zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zip( @@ -5217,6 +5406,7 @@ public static Flowable zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zip( @@ -5287,6 +5477,7 @@ public static Flowable zip( * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zipArray(Function zipper, @@ -5349,6 +5540,7 @@ public static Flowable zipArray(FunctionReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable zipIterable(Iterable> sources, @@ -5384,6 +5576,7 @@ public static Flowable zipIterable(IterableReactiveX operators documentation: All */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single all(Predicate predicate) { @@ -5413,6 +5606,7 @@ public final Single all(Predicate predicate) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable ambWith(Publisher other) { @@ -5444,6 +5638,7 @@ public final Flowable ambWith(Publisher other) { * @see ReactiveX operators documentation: Contains */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single any(Predicate predicate) { @@ -5838,15 +6033,16 @@ public final T blockingSingle(T defaultItem) { } /** - * Returns a {@link Future} representing the single value emitted by this {@code Flowable}. + * Returns a {@link Future} representing the only value emitted by this {@code Flowable}. + *

+ * *

* If the {@link Flowable} emits more than one item, {@link java.util.concurrent.Future} will receive an - * {@link java.lang.IllegalArgumentException}. If the {@link Flowable} is empty, {@link java.util.concurrent.Future} - * will receive a {@link java.util.NoSuchElementException}. + * {@link java.lang.IndexOutOfBoundsException}. If the {@link Flowable} is empty, {@link java.util.concurrent.Future} + * will receive a {@link java.util.NoSuchElementException}. The {@code Flowable} source has to terminate in order + * for the returned {@code Future} to terminate as well. *

* If the {@code Flowable} may emit more than one item, use {@code Flowable.toList().toFuture()}. - *

- * *

*
Backpressure:
*
The operator consumes the source {@code Flowable} in an unbounded manner @@ -6173,6 +6369,7 @@ public final Flowable> buffer(int count, int skip) { * @see ReactiveX operators documentation: Buffer */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final > Flowable buffer(int count, int skip, Callable bufferSupplier) { @@ -6322,6 +6519,7 @@ public final Flowable> buffer(long timespan, long timeskip, TimeUnit uni * @see ReactiveX operators documentation: Buffer */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final > Flowable buffer(long timespan, long timeskip, TimeUnit unit, @@ -6935,6 +7133,7 @@ public final Flowable cacheWithInitialCapacity(int initialCapacity) { * @see ReactiveX operators documentation: Map */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable cast(final Class clazz) { @@ -6972,6 +7171,7 @@ public final Flowable cast(final Class clazz) { * @see ReactiveX operators documentation: Reduce */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single collect(Callable initialItemSupplier, BiConsumer collector) { @@ -7010,6 +7210,7 @@ public final Single collect(Callable initialItemSupplier, Bi * @see ReactiveX operators documentation: Reduce */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single collectInto(final U initialItem, BiConsumer collector) { @@ -7107,6 +7308,7 @@ public final Flowable concatMap(FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable concatMap(Function> mapper, int prefetch) { @@ -7175,6 +7377,7 @@ public final Completable concatMapCompletable(Function mapper, int prefetch) { @@ -7277,6 +7480,7 @@ public final Completable concatMapCompletableDelayError(Function mapper, boolean tillTheEnd, int prefetch) { @@ -7340,6 +7544,7 @@ public final Flowable concatMapDelayError(Function Flowable concatMapDelayError(Function> mapper, @@ -7357,7 +7562,6 @@ public final Flowable concatMapDelayError(Function(this, mapper, prefetch, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY)); } - /** * Maps a sequence of values into Publishers and concatenates these Publishers eagerly into a single * Publisher. @@ -7408,6 +7612,7 @@ public final Flowable concatMapEager(Function Flowable concatMapEager(Function> mapper, @@ -7477,6 +7682,7 @@ public final Flowable concatMapEagerDelayError(Function Flowable concatMapEagerDelayError(Function> mapper, @@ -7541,6 +7747,7 @@ public final Flowable concatMapIterable(FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable concatMapIterable(final Function> mapper, int prefetch) { @@ -7609,6 +7816,7 @@ public final Flowable concatMapMaybe(Function Flowable concatMapMaybe(Function> mapper, int prefetch) { @@ -7719,6 +7927,7 @@ public final Flowable concatMapMaybeDelayError(Function Flowable concatMapMaybeDelayError(Function> mapper, boolean tillTheEnd, int prefetch) { @@ -7787,6 +7996,7 @@ public final Flowable concatMapSingle(Function Flowable concatMapSingle(Function> mapper, int prefetch) { @@ -7897,6 +8107,7 @@ public final Flowable concatMapSingleDelayError(Function Flowable concatMapSingleDelayError(Function> mapper, boolean tillTheEnd, int prefetch) { @@ -7926,6 +8137,7 @@ public final Flowable concatMapSingleDelayError(FunctionReactiveX operators documentation: Concat */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable concatWith(Publisher other) { @@ -8030,6 +8242,7 @@ public final Flowable concatWith(@NonNull CompletableSource other) { * @see ReactiveX operators documentation: Contains */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single contains(final Object item) { @@ -8053,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) @@ -8067,6 +8279,14 @@ public final Single count() { * source Publisher that are followed by another item within a computed debounce duration. *

* + *

+ * The delivery of the item happens on the thread of the first {@code onNext} or {@code onComplete} + * signal of the generated {@code Publisher} sequence, + * which if takes too long, a newer item may arrive from the upstream, causing the + * generated sequence to get cancelled, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. *

*
Backpressure:
*
This operator does not support backpressure as it uses the {@code debounceSelector} to mark @@ -8085,6 +8305,7 @@ public final Single count() { * @see RxJava wiki: Backpressure */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable debounce(Function> debounceIndicator) { @@ -8101,6 +8322,13 @@ public final Flowable debounce(Function * will be emitted by the resulting Publisher. *

* + *

+ * Delivery of the item after the grace period happens on the {@code computation} {@code Scheduler}'s + * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the + * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. *

*
Backpressure:
*
This operator does not support backpressure as it uses time to control data flow.
@@ -8136,6 +8364,13 @@ public final Flowable debounce(long timeout, TimeUnit unit) { * will be emitted by the resulting Publisher. *

* + *

+ * Delivery of the item after the grace period happens on the given {@code Scheduler}'s + * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the + * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. *

*
Backpressure:
*
This operator does not support backpressure as it uses time to control data flow.
@@ -8158,6 +8393,7 @@ public final Flowable debounce(long timeout, TimeUnit unit) { * @see #throttleWithTimeout(long, TimeUnit, Scheduler) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable debounce(long timeout, TimeUnit unit, Scheduler scheduler) { @@ -8188,10 +8424,11 @@ public final Flowable debounce(long timeout, TimeUnit unit, Scheduler schedul * @see ReactiveX operators documentation: DefaultIfEmpty */ @CheckReturnValue + @NonNull @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)); } @@ -8223,6 +8460,7 @@ public final Flowable defaultIfEmpty(T defaultItem) { * @see ReactiveX operators documentation: Delay */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable delay(final Function> itemDelayIndicator) { @@ -8338,6 +8576,7 @@ public final Flowable delay(long delay, TimeUnit unit, Scheduler scheduler) { * @see ReactiveX operators documentation: Delay */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable delay(long delay, TimeUnit unit, Scheduler scheduler, boolean delayError) { @@ -8406,6 +8645,7 @@ public final Flowable delay(Publisher subscriptionIndicator, * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable delaySubscription(Publisher subscriptionIndicator) { @@ -8480,6 +8720,7 @@ public final Flowable delaySubscription(long delay, TimeUnit unit, Scheduler *

      * Flowable.just(createOnNext(1), createOnComplete(), createOnNext(2))
      * .doOnCancel(() -> System.out.println("Cancelled!"));
+     * .dematerialize()
      * .test()
      * .assertResult(1);
      * 
@@ -8487,6 +8728,7 @@ public final Flowable delaySubscription(long delay, TimeUnit unit, Scheduler * with the same event. *

      * Flowable.just(createOnNext(1), createOnNext(2))
+     * .dematerialize()
      * .test()
      * .assertResult(1, 2);
      * 
@@ -8504,14 +8746,75 @@ public final Flowable delaySubscription(long delay, TimeUnit unit, Scheduler * @return a Flowable that emits the items and notifications embedded in the {@link Notification} objects * emitted by the source Publisher * @see ReactiveX operators documentation: Dematerialize + * @see #dematerialize(Function) + * @deprecated in 2.2.4; inherently type-unsafe as it overrides the output generic type. Use {@link #dematerialize(Function)} instead. */ @CheckReturnValue - @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @Deprecated + @SuppressWarnings({ "unchecked", "rawtypes" }) public final Flowable dematerialize() { - @SuppressWarnings("unchecked") - Flowable> m = (Flowable>)this; - return RxJavaPlugins.onAssembly(new FlowableDematerialize(m)); + return RxJavaPlugins.onAssembly(new FlowableDematerialize(this, Functions.identity())); + } + + /** + * Returns a Flowable that reverses the effect of {@link #materialize materialize} by transforming the + * {@link Notification} objects extracted from the source items via a selector function + * into their respective {@code Subscriber} signal types. + *

+ * + *

+ * 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 + * limiting instance methods to a certain generic argument shape, therefore, + * a function is used to ensure the conversion remains type safe. + *

+ * When the upstream signals an {@link Notification#createOnError(Throwable) onError} or + * {@link Notification#createOnComplete() onComplete} item, the + * returned Flowable cancels of the flow and terminates with that type of terminal event: + *


+     * Flowable.just(createOnNext(1), createOnComplete(), createOnNext(2))
+     * .doOnCancel(() -> System.out.println("Canceled!"));
+     * .dematerialize(notification -> notification)
+     * .test()
+     * .assertResult(1);
+     * 
+ * If the upstream signals {@code onError} or {@code onComplete} directly, the flow is terminated + * with the same event. + *

+     * Flowable.just(createOnNext(1), createOnNext(2))
+     * .dematerialize(notification -> notification)
+     * .test()
+     * .assertResult(1, 2);
+     * 
+ * If this behavior is not desired, the completion can be suppressed by applying {@link #concatWith(Publisher)} + * with a {@link #never()} source. + *
+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code dematerialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the output value type + * @param selector function that returns the upstream item and should return a Notification to signal + * the corresponding {@code Subscriber} event to the downstream. + * @return a Flowable that emits the items and notifications embedded in the {@link Notification} objects + * selected from the items emitted by the source Flowable + * @see ReactiveX operators documentation: Dematerialize + * @since 2.2.4 - experimental + */ + @Experimental + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final Flowable dematerialize(Function> selector) { + ObjectHelper.requireNonNull(selector, "selector is null"); + return RxJavaPlugins.onAssembly(new FlowableDematerialize(this, selector)); } /** @@ -8649,6 +8952,13 @@ public final Flowable distinct(Function keySelector, *

* Note that the operator always retains the latest item from upstream regardless of the comparison result * and uses it in the next comparison with the next upstream item. + *

+ * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. *

*
Backpressure:
*
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s @@ -8685,6 +8995,13 @@ public final Flowable distinctUntilChanged() { *

* Note that the operator always retains the latest key from upstream regardless of the comparison result * and uses it in the next comparison with the next key derived from the next upstream item. + *

+ * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. *

*
Backpressure:
*
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s @@ -8717,6 +9034,13 @@ public final Flowable distinctUntilChanged(Function keySele *

* Note that the operator always retains the latest item from upstream regardless of the comparison result * and uses it in the next comparison with the next upstream item. + *

+ * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. *

*
Backpressure:
*
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s @@ -8899,6 +9223,7 @@ public final Flowable doOnComplete(Action onComplete) { * @see ReactiveX operators documentation: Do */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) private Flowable doOnEach(Consumer onNext, Consumer onError, @@ -8928,10 +9253,11 @@ private Flowable doOnEach(Consumer onNext, ConsumerReactiveX operators documentation: Do */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable doOnEach(final Consumer> onNotification) { - ObjectHelper.requireNonNull(onNotification, "consumer is null"); + ObjectHelper.requireNonNull(onNotification, "onNotification is null"); return doOnEach( Functions.notificationOnNext(onNotification), Functions.notificationOnError(onNotification), @@ -8964,6 +9290,7 @@ public final Flowable doOnEach(final Consumer> onNoti * @see ReactiveX operators documentation: Do */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable doOnEach(final Subscriber subscriber) { @@ -9026,6 +9353,7 @@ public final Flowable doOnError(Consumer onError) { * @see ReactiveX operators documentation: Do */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable doOnLifecycle(final Consumer onSubscribe, @@ -9177,7 +9505,7 @@ public final Maybe elementAt(long index) { } /** - * Returns a Flowable that emits the item found at a specified index in a sequence of emissions from + * Returns a Single that emits the item found at a specified index in a sequence of emissions from * this Flowable, or a default item if that index is out of range. *

* @@ -9193,13 +9521,14 @@ public final Maybe elementAt(long index) { * the zero-based index of the item to retrieve * @param defaultItem * the default item - * @return a Flowable that emits the item at the specified position in the sequence emitted by the source + * @return a Single that emits the item at the specified position in the sequence emitted by the source * Publisher, or the default item if that index is outside the bounds of the source sequence * @throws IndexOutOfBoundsException * if {@code index} is less than 0 * @see ReactiveX operators documentation: ElementAt */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single elementAt(long index, T defaultItem) { @@ -9211,7 +9540,7 @@ public final Single elementAt(long index, T defaultItem) { } /** - * Returns a Flowable that emits the item found at a specified index in a sequence of emissions from + * Returns a Single that emits the item found at a specified index in a sequence of emissions from * this Flowable or signals a {@link NoSuchElementException} if this Flowable has fewer elements than index. *

* @@ -9225,7 +9554,7 @@ public final Single elementAt(long index, T defaultItem) { * * @param index * the zero-based index of the item to retrieve - * @return a Flowable that emits the item at the specified position in the sequence emitted by the source + * @return a Single that emits the item at the specified position in the sequence emitted by the source * Publisher, or the default item if that index is outside the bounds of the source sequence * @throws IndexOutOfBoundsException * if {@code index} is less than 0 @@ -9261,6 +9590,7 @@ public final Single elementAtOrError(long index) { * @see ReactiveX operators documentation: Filter */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable filter(Predicate predicate) { @@ -9517,6 +9847,7 @@ public final Flowable flatMap(Function Flowable flatMap(Function> mapper, @@ -9565,6 +9896,7 @@ public final Flowable flatMap(FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable flatMap( @@ -9611,6 +9943,7 @@ public final Flowable flatMap( * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable flatMap( @@ -9781,6 +10114,7 @@ public final Flowable flatMap(Function Flowable flatMap(final Function> mapper, @@ -9869,6 +10203,7 @@ public final Completable flatMapCompletable(Function mapper, boolean delayErrors, int maxConcurrency) { @@ -9933,6 +10268,7 @@ public final Flowable flatMapIterable(final FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable flatMapIterable(final Function> mapper, int bufferSize) { @@ -9969,6 +10305,7 @@ public final Flowable flatMapIterable(final FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable flatMapIterable(final Function> mapper, @@ -10011,6 +10348,7 @@ public final Flowable flatMapIterable(final Function Flowable flatMapIterable(final Function> mapper, @@ -10060,6 +10398,7 @@ public final Flowable flatMapMaybe(Function Flowable flatMapMaybe(Function> mapper, boolean delayErrors, int maxConcurrency) { @@ -10108,6 +10447,7 @@ public final Flowable flatMapSingle(Function Flowable flatMapSingle(Function> mapper, boolean delayErrors, int maxConcurrency) { @@ -10228,6 +10568,7 @@ public final Disposable forEachWhile(Predicate onNext, ConsumerReactiveX operators documentation: Subscribe */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.NONE) @SchedulerSupport(SchedulerSupport.NONE) public final Disposable forEachWhile(final Predicate onNext, final Consumer onError, @@ -10254,6 +10595,14 @@ public final Disposable forEachWhile(final Predicate onNext, final Co * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those * {@code GroupedPublisher}s that do not concern you. Instead, you can signal to them that they may * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * *

*
Backpressure:
*
Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} @@ -10294,6 +10643,13 @@ public final Flowable> groupBy(Function + * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. *
*
Backpressure:
*
Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} @@ -10337,6 +10693,14 @@ public final Flowable> groupBy(Function + * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * *
*
Backpressure:
*
Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} @@ -10382,6 +10746,14 @@ public final Flowable> groupBy(Function + * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * *
*
Backpressure:
*
Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} @@ -10430,6 +10802,14 @@ public final Flowable> groupBy(Function + * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * *
*
Backpressure:
*
Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} @@ -10460,6 +10840,7 @@ public final Flowable> groupBy(FunctionReactiveX operators documentation: GroupBy */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable> groupBy(Function keySelector, @@ -10526,6 +10907,14 @@ public final Flowable> groupBy(Function + * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * *
*
Backpressure:
*
Both the returned and its inner {@code GroupedFlowable}s honor backpressure and the source {@code Publisher} @@ -10564,6 +10953,7 @@ public final Flowable> groupBy(Function Flowable> groupBy(Function keySelector, @@ -10613,6 +11003,7 @@ public final Flowable> groupBy(FunctionReactiveX operators documentation: Join */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable groupJoin( @@ -10734,6 +11125,7 @@ public final Single isEmpty() { * @see ReactiveX operators documentation: Join */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable join( @@ -10749,7 +11141,6 @@ public final Flowable join( this, other, leftEnd, rightEnd, resultSelector)); } - /** * Returns a Maybe that emits the last item emitted by this Flowable or completes if * this Flowable is empty. @@ -10792,6 +11183,7 @@ public final Maybe lastElement() { * @see ReactiveX operators documentation: Last */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single last(T defaultItem) { @@ -10969,6 +11361,7 @@ public final Single lastOrError() { * @see #compose(FlowableTransformer) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.SPECIAL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable lift(FlowableOperator lifter) { @@ -11043,6 +11436,7 @@ public final Flowable limit(long count) { * @see ReactiveX operators documentation: Map */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable map(Function mapper) { @@ -11066,6 +11460,7 @@ public final Flowable map(Function mapper) { * @return a Flowable that emits items that are the result of materializing the items and notifications * of the source Publisher * @see ReactiveX operators documentation: Materialize + * @see #dematerialize(Function) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -11095,6 +11490,7 @@ public final Flowable> materialize() { * @see ReactiveX operators documentation: Merge */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable mergeWith(Publisher other) { @@ -11122,6 +11518,7 @@ public final Flowable mergeWith(Publisher other) { * @since 2.2 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable mergeWith(@NonNull SingleSource other) { @@ -11150,6 +11547,7 @@ public final Flowable mergeWith(@NonNull SingleSource other) { * @since 2.2 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable mergeWith(@NonNull MaybeSource other) { @@ -11175,6 +11573,7 @@ public final Flowable mergeWith(@NonNull MaybeSource other) { * @since 2.2 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable mergeWith(@NonNull CompletableSource other) { @@ -11190,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 @@ -11210,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) @@ -11223,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 @@ -11247,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) @@ -11260,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 @@ -11285,8 +11701,10 @@ 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 @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable observeOn(Scheduler scheduler, boolean delayError, int bufferSize) { @@ -11314,6 +11732,7 @@ public final Flowable observeOn(Scheduler scheduler, boolean delayError, int * @see ReactiveX operators documentation: Filter */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable ofType(final Class clazz) { @@ -11458,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)); } @@ -11490,6 +11909,7 @@ public final Flowable onBackpressureBuffer(int capacity, boolean delayError, * @since 1.1.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.SPECIAL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable onBackpressureBuffer(int capacity, boolean delayError, boolean unbounded, @@ -11561,10 +11981,11 @@ public final Flowable onBackpressureBuffer(int capacity, Action onOverflow) { * @since 2.0 */ @CheckReturnValue + @NonNull @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)); } @@ -11617,6 +12038,7 @@ public final Flowable onBackpressureDrop() { * @since 1.1.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable onBackpressureDrop(Consumer onDrop) { @@ -11692,6 +12114,7 @@ public final Flowable onBackpressureLatest() { * @see ReactiveX operators documentation: Catch */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable onErrorResumeNext(Function> resumeFunction) { @@ -11735,6 +12158,7 @@ public final Flowable onErrorResumeNext(FunctionReactiveX operators documentation: Catch */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable onErrorResumeNext(final Publisher next) { @@ -11774,6 +12198,7 @@ public final Flowable onErrorResumeNext(final Publisher next) { * @see ReactiveX operators documentation: Catch */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable onErrorReturn(Function valueSupplier) { @@ -11813,6 +12238,7 @@ public final Flowable onErrorReturn(Function * @see ReactiveX operators documentation: Catch */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable onErrorReturnItem(final T item) { @@ -11859,6 +12285,7 @@ public final Flowable onErrorReturnItem(final T item) { * @see ReactiveX operators documentation: Catch */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable onExceptionResumeNext(final Publisher next) { @@ -12067,6 +12494,7 @@ public final Flowable publish(Function, ? extends Pub * @see ReactiveX operators documentation: Publish */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable publish(Function, ? extends Publisher> selector, int prefetch) { @@ -12135,8 +12563,6 @@ public final Flowable rebatchRequests(int n) { * Publisher into the same function, and so on until all items have been emitted by the finite source Publisher, * and emits the final result from the final call to your function as its sole item. *

- * If the source is empty, a {@code NoSuchElementException} is signaled. - *

* *

* This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," @@ -12163,6 +12589,7 @@ public final Flowable rebatchRequests(int n) { * @see Wikipedia: Fold (higher-order function) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Maybe reduce(BiFunction reducer) { @@ -12224,6 +12651,7 @@ public final Maybe reduce(BiFunction reducer) { * @see #reduceWith(Callable, BiFunction) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single reduce(R seed, BiFunction reducer) { @@ -12268,6 +12696,7 @@ public final Single reduce(R seed, BiFunction reducer) { * @see Wikipedia: Fold (higher-order function) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single reduceWith(Callable seedSupplier, BiFunction reducer) { @@ -12355,6 +12784,7 @@ public final Flowable repeat(long times) { * @see ReactiveX operators documentation: Repeat */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable repeatUntil(BooleanSupplier stop) { @@ -12385,6 +12815,7 @@ public final Flowable repeatUntil(BooleanSupplier stop) { * @see ReactiveX operators documentation: Repeat */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable repeatWhen(final Function, ? extends Publisher> handler) { @@ -12443,6 +12874,7 @@ public final ConnectableFlowable replay() { * @see ReactiveX operators documentation: Replay */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable replay(Function, ? extends Publisher> selector) { @@ -12481,6 +12913,7 @@ public final Flowable replay(Function, ? extends Publ * @see ReactiveX operators documentation: Replay */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable replay(Function, ? extends Publisher> selector, final int bufferSize) { @@ -12571,6 +13004,7 @@ public final Flowable replay(Function, ? extends Publ * @see ReactiveX operators documentation: Replay */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable replay(Function, ? extends Publisher> selector, final int bufferSize, final long time, final TimeUnit unit, final Scheduler scheduler) { @@ -12615,6 +13049,7 @@ public final Flowable replay(Function, ? extends Publ * @see ReactiveX operators documentation: Replay */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable replay(final Function, ? extends Publisher> selector, final int bufferSize, final Scheduler scheduler) { @@ -12694,6 +13129,7 @@ public final Flowable replay(Function, ? extends Publ * @see ReactiveX operators documentation: Replay */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable replay(Function, ? extends Publisher> selector, final long time, final TimeUnit unit, final Scheduler scheduler) { @@ -12730,6 +13166,7 @@ public final Flowable replay(Function, ? extends Publ * @see ReactiveX operators documentation: Replay */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable replay(final Function, ? extends Publisher> selector, final Scheduler scheduler) { @@ -13038,6 +13475,7 @@ public final Flowable retry() { * @see ReactiveX operators documentation: Retry */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable retry(BiPredicate predicate) { @@ -13069,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 */ @@ -13090,11 +13528,12 @@ 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 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable retry(long times, Predicate predicate) { @@ -13139,6 +13578,7 @@ public final Flowable retry(Predicate predicate) { * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable retryUntil(final BooleanSupplier stop) { @@ -13224,6 +13664,7 @@ public final Flowable retryUntil(final BooleanSupplier stop) { * @see ReactiveX operators documentation: Retry */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable retryWhen( @@ -13237,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.
@@ -13347,6 +13788,7 @@ public final Flowable sample(long period, TimeUnit unit, boolean emitLast) { * @see #throttleLast(long, TimeUnit, Scheduler) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable sample(long period, TimeUnit unit, Scheduler scheduler) { @@ -13387,6 +13829,7 @@ public final Flowable sample(long period, TimeUnit unit, Scheduler scheduler) * @since 2.1 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable sample(long period, TimeUnit unit, Scheduler scheduler, boolean emitLast) { @@ -13418,6 +13861,7 @@ public final Flowable sample(long period, TimeUnit unit, Scheduler scheduler, * @see RxJava wiki: Backpressure */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable sample(Publisher sampler) { @@ -13455,6 +13899,7 @@ public final Flowable sample(Publisher sampler) { * @since 2.1 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable sample(Publisher sampler, boolean emitLast) { @@ -13487,6 +13932,7 @@ public final Flowable sample(Publisher sampler, boolean emitLast) { * @see ReactiveX operators documentation: Scan */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable scan(BiFunction accumulator) { @@ -13540,10 +13986,11 @@ public final Flowable scan(BiFunction accumulator) { * @see ReactiveX operators documentation: Scan */ @CheckReturnValue + @NonNull @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); } @@ -13579,6 +14026,7 @@ public final Flowable scan(final R initialValue, BiFunctionReactiveX operators documentation: Scan */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable scanWith(Callable seedSupplier, BiFunction accumulator) { @@ -13690,6 +14138,7 @@ public final Maybe singleElement() { * @see ReactiveX operators documentation: First */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single single(T defaultItem) { @@ -14011,6 +14460,7 @@ public final Flowable skipLast(long time, TimeUnit unit, Scheduler scheduler, * @see ReactiveX operators documentation: SkipLast */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable skipLast(long time, TimeUnit unit, Scheduler scheduler, boolean delayError, int bufferSize) { @@ -14044,6 +14494,7 @@ public final Flowable skipLast(long time, TimeUnit unit, Scheduler scheduler, * @see ReactiveX operators documentation: SkipUntil */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable skipUntil(Publisher other) { @@ -14071,6 +14522,7 @@ public final Flowable skipUntil(Publisher other) { * @see ReactiveX operators documentation: SkipWhile */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable skipWhile(Predicate predicate) { @@ -14126,6 +14578,7 @@ public final Flowable sorted() { * @return a Flowable that emits the items emitted by the source Publisher in sorted order */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable sorted(Comparator sortFunction) { @@ -14183,6 +14636,7 @@ public final Flowable startWith(Iterable items) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable startWith(Publisher other) { @@ -14212,10 +14666,11 @@ public final Flowable startWith(Publisher other) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @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); } @@ -14397,10 +14852,12 @@ public final Disposable subscribe(Consumer onNext, ConsumerReactiveX operators documentation: Subscribe */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.SPECIAL) @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe(Consumer onNext, Consumer onError, @@ -14444,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.
  • @@ -14473,7 +14930,7 @@ public final void subscribe(FlowableSubscriber s) { try { Subscriber z = RxJavaPlugins.onSubscribe(this, s); - ObjectHelper.requireNonNull(z, "Plugin returned null Subscriber"); + ObjectHelper.requireNonNull(z, "The RxJavaPlugins.onSubscribe hook returned a null FlowableSubscriber. Please check the handler provided to RxJavaPlugins.setOnFlowableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); subscribeActual(z); } catch (NullPointerException e) { // NOPMD @@ -14561,6 +15018,7 @@ public final > E subscribeWith(E subscriber) { * @see #subscribeOn(Scheduler, boolean) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable subscribeOn(@NonNull Scheduler scheduler) { @@ -14598,6 +15056,7 @@ public final Flowable subscribeOn(@NonNull Scheduler scheduler) { * @since 2.2 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable subscribeOn(@NonNull Scheduler scheduler, boolean requestOn) { @@ -14628,6 +15087,7 @@ public final Flowable subscribeOn(@NonNull Scheduler scheduler, boolean reque * @since 1.1.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable switchIfEmpty(Publisher other) { @@ -14660,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) @@ -14695,6 +15156,7 @@ public final Flowable switchMap(FunctionReactiveX operators documentation: FlatMap + * @see #switchMapDelayError(Function, int) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -14703,7 +15165,6 @@ public final Flowable switchMap(Function Flowable switchMap(Function mapper) { @@ -14784,10 +15246,11 @@ public final Completable switchMapCompletable(@NonNull Function mapper) { @@ -14821,6 +15284,7 @@ public final Completable switchMapCompletableDelayError(@NonNull FunctionReactiveX operators documentation: FlatMap + * @see #switchMap(Function) * @since 2.0 */ @CheckReturnValue @@ -14858,6 +15322,7 @@ public final Flowable switchMapDelayError(FunctionReactiveX operators documentation: FlatMap + * @see #switchMap(Function, int) * @since 2.0 */ @CheckReturnValue @@ -14911,9 +15376,11 @@ Flowable switchMap0(Function> * and get subscribed to. * @return the new Flowable instance * @see #switchMapMaybe(Function) + * @see #switchMapMaybeDelayError(Function) * @since 2.2 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable switchMapMaybe(@NonNull Function> mapper) { @@ -14944,6 +15411,7 @@ public final Flowable switchMapMaybe(@NonNull Function Flowable switchMapMaybeDelayError(@NonNull Function> mapper) { @@ -14980,10 +15448,11 @@ public final Flowable switchMapMaybeDelayError(@NonNull Function Flowable switchMapSingle(@NonNull Function> mapper) { @@ -15014,6 +15483,7 @@ public final Flowable switchMapSingle(@NonNull Function Flowable switchMapSingleDelayError(@NonNull Function> mapper) { @@ -15257,6 +15727,7 @@ public final Flowable takeLast(long count, long time, TimeUnit unit, Schedule * @see ReactiveX operators documentation: TakeLast */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable takeLast(long count, long time, TimeUnit unit, Scheduler scheduler, boolean delayError, int bufferSize) { @@ -15468,6 +15939,7 @@ public final Flowable takeLast(long time, TimeUnit unit, Scheduler scheduler, * @since 1.1.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable takeUntil(Predicate stopPredicate) { @@ -15497,6 +15969,7 @@ public final Flowable takeUntil(Predicate stopPredicate) { * @see ReactiveX operators documentation: TakeUntil */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable takeUntil(Publisher other) { @@ -15525,6 +15998,7 @@ public final Flowable takeUntil(Publisher other) { * @see Flowable#takeUntil(Predicate) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable takeWhile(Predicate predicate) { @@ -15589,6 +16063,7 @@ public final Flowable throttleFirst(long windowDuration, TimeUnit unit) { * @see RxJava wiki: Backpressure */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable throttleFirst(long skipDuration, TimeUnit unit, Scheduler scheduler) { @@ -15808,6 +16283,7 @@ public final Flowable throttleLatest(long timeout, TimeUnit unit, Scheduler s * @since 2.2 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable throttleLatest(long timeout, TimeUnit unit, Scheduler scheduler, boolean emitLast) { @@ -16061,6 +16537,7 @@ public final Flowable timeout(Function> * @see ReactiveX operators documentation: Timeout */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable timeout(Function> itemTimeoutIndicator, Flowable other) { @@ -16100,7 +16577,7 @@ public final Flowable timeout(long timeout, TimeUnit timeUnit) { /** * Returns a Flowable that mirrors the source Publisher but applies a timeout policy for each emitted * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, - * the resulting Publisher begins instead to mirror a fallback Publisher. + * the source Publisher is disposed and resulting Publisher begins instead to mirror a fallback Publisher. *

    * *

    @@ -16123,6 +16600,7 @@ public final Flowable timeout(long timeout, TimeUnit timeUnit) { * @see ReactiveX operators documentation: Timeout */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.COMPUTATION) public final Flowable timeout(long timeout, TimeUnit timeUnit, Publisher other) { @@ -16133,7 +16611,8 @@ public final Flowable timeout(long timeout, TimeUnit timeUnit, Publisher * *
    @@ -16159,6 +16638,7 @@ public final Flowable timeout(long timeout, TimeUnit timeUnit, PublisherReactiveX operators documentation: Timeout */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler, Publisher other) { @@ -16230,6 +16710,7 @@ public final Flowable timeout(long timeout, TimeUnit timeUnit, Scheduler sche * @see ReactiveX operators documentation: Timeout */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable timeout(Publisher firstTimeoutIndicator, @@ -16275,6 +16756,7 @@ public final Flowable timeout(Publisher firstTimeoutIndicator, * @see ReactiveX operators documentation: Timeout */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable timeout( @@ -16399,6 +16881,7 @@ public final Flowable> timestamp(TimeUnit unit) { * @see ReactiveX operators documentation: Timestamp */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) // Supplied scheduler is only used for creating timestamps. public final Flowable> timestamp(final TimeUnit unit, final Scheduler scheduler) { @@ -16569,6 +17052,7 @@ public final > Single toList(Callable coll * @see ReactiveX operators documentation: To */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single> toMap(final Function keySelector) { @@ -16607,6 +17091,7 @@ public final Single> toMap(final Function * @see ReactiveX operators documentation: To */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single> toMap(final Function keySelector, final Function valueSelector) { @@ -16645,6 +17130,7 @@ public final Single> toMap(final FunctionReactiveX operators documentation: To */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single> toMap(final Function keySelector, @@ -16758,6 +17244,7 @@ public final Single>> toMultimap(FunctionReactiveX operators documentation: To */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single>> toMultimap( @@ -16826,7 +17313,7 @@ public final Single>> toMultimap( * @since 2.0 */ @CheckReturnValue - @BackpressureSupport(BackpressureKind.NONE) + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Observable toObservable() { return RxJavaPlugins.onAssembly(new ObservableFromPublisher(this)); @@ -16889,6 +17376,7 @@ public final Single> toSortedList() { * @see ReactiveX operators documentation: To */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single> toSortedList(final Comparator comparator) { @@ -16924,6 +17412,7 @@ public final Single> toSortedList(final Comparator comparator * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single> toSortedList(final Comparator comparator, int capacityHint) { @@ -16985,6 +17474,7 @@ public final Single> toSortedList(int capacityHint) { * @see ReactiveX operators documentation: SubscribeOn */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable unsubscribeOn(Scheduler scheduler) { @@ -17195,6 +17685,7 @@ public final Flowable> window(long timespan, long timeskip, TimeUnit * @see ReactiveX operators documentation: Window */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable> window(long timespan, long timeskip, TimeUnit unit, Scheduler scheduler, int bufferSize) { @@ -17473,6 +17964,7 @@ public final Flowable> window(long timespan, TimeUnit unit, * @see ReactiveX operators documentation: Window */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable> window( @@ -17541,6 +18033,7 @@ public final Flowable> window(Publisher boundaryIndicator) { * @see ReactiveX operators documentation: Window */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable> window(Publisher boundaryIndicator, int bufferSize) { @@ -17617,6 +18110,7 @@ public final Flowable> window( * @see ReactiveX operators documentation: Window */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable> window( @@ -17690,6 +18184,7 @@ public final Flowable> window(Callable> b * @see ReactiveX operators documentation: Window */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable> window(Callable> boundaryIndicatorSupplier, int bufferSize) { @@ -17727,6 +18222,7 @@ public final Flowable> window(Callable> b * @see ReactiveX operators documentation: CombineLatest */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable withLatestFrom(Publisher other, @@ -17764,6 +18260,7 @@ public final Flowable withLatestFrom(Publisher other, * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable withLatestFrom(Publisher source1, Publisher source2, @@ -17803,6 +18300,7 @@ public final Flowable withLatestFrom(Publisher source1, Publi * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable withLatestFrom( @@ -17847,6 +18345,7 @@ public final Flowable withLatestFrom( * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable withLatestFrom( @@ -17885,6 +18384,7 @@ public final Flowable withLatestFrom( * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable withLatestFrom(Publisher[] others, Function combiner) { @@ -17917,6 +18417,7 @@ public final Flowable withLatestFrom(Publisher[] others, Function Flowable withLatestFrom(Iterable> others, Function combiner) { @@ -17956,6 +18457,7 @@ public final Flowable withLatestFrom(Iterable> oth * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable zipWith(Iterable other, BiFunction zipper) { @@ -17979,7 +18481,7 @@ public final Flowable zipWith(Iterable other, BiFunctionTo work around this termination property, * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. - * + *

    * *

    *
    Backpressure:
    @@ -18004,6 +18506,7 @@ public final Flowable zipWith(Iterable other, BiFunctionReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable zipWith(Publisher other, BiFunction zipper) { @@ -18026,7 +18529,7 @@ public final Flowable zipWith(Publisher other, BiFunction *
    To work around this termination property, * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. - * + *

    * *

    *
    Backpressure:
    @@ -18076,7 +18579,7 @@ public final Flowable zipWith(Publisher other, *
    To work around this termination property, * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. - * + *

    * *

    *
    Backpressure:
    diff --git a/src/main/java/io/reactivex/FlowableSubscriber.java b/src/main/java/io/reactivex/FlowableSubscriber.java index 2ef1019acf..e2636406e1 100644 --- a/src/main/java/io/reactivex/FlowableSubscriber.java +++ b/src/main/java/io/reactivex/FlowableSubscriber.java @@ -17,7 +17,7 @@ import org.reactivestreams.*; /** - * Represents a Reactive-Streams inspired Subscriber that is RxJava 2 only + * Represents a Reactive Streams inspired Subscriber that is RxJava 2 only * and weakens rules §1.3 and §3.9 of the specification for gaining performance. * *

    History: 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 519a64f6b9..aecb501348 100644 --- a/src/main/java/io/reactivex/Maybe.java +++ b/src/main/java/io/reactivex/Maybe.java @@ -93,7 +93,7 @@ * d.dispose(); * *

    - * Note that by design, subscriptions via {@link #subscribe(MaybeObserver)} can't be cancelled/disposed + * Note that by design, subscriptions via {@link #subscribe(MaybeObserver)} can't be disposed * from the outside (hence the * {@code void} return of the {@link #subscribe(MaybeObserver)} method) and it is the * responsibility of the implementor of the {@code MaybeObserver} to allow this to happen. @@ -110,7 +110,7 @@ public abstract class Maybe implements MaybeSource { /** - * Runs multiple MaybeSources and signals the events of the first one that signals (cancelling + * Runs multiple MaybeSources and signals the events of the first one that signals (disposing * the rest). *

    * @@ -124,6 +124,7 @@ public abstract class Maybe implements MaybeSource { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe amb(final Iterable> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -131,7 +132,7 @@ public static Maybe amb(final Iterable } /** - * Runs multiple MaybeSources and signals the events of the first one that signals (cancelling + * Runs multiple MaybeSources and signals the events of the first one that signals (disposing * the rest). *

    * @@ -174,6 +175,7 @@ public static Maybe ambArray(final MaybeSource... sources) { */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Flowable concat(Iterable> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -201,6 +203,7 @@ public static Flowable concat(Iterable */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Flowable concat(MaybeSource source1, MaybeSource source2) { @@ -232,6 +235,7 @@ public static Flowable concat(MaybeSource source1, MaybeSour */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Flowable concat( @@ -267,6 +271,7 @@ public static Flowable concat( */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Flowable concat( @@ -322,6 +327,7 @@ public static Flowable concat(Publisher Flowable concat(Publisher> sources, int prefetch) { @@ -346,6 +352,7 @@ public static Flowable concat(Publisher Flowable concatArray(MaybeSource... sources) { @@ -434,6 +441,7 @@ public static Flowable concatArrayEager(MaybeSource... sourc @SuppressWarnings({ "unchecked", "rawtypes" }) @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Flowable concatDelayError(Iterable> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -557,6 +565,7 @@ public static Flowable concatEager(Publisher Maybe create(MaybeOnSubscribe onSubscribe) { ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); @@ -576,6 +585,7 @@ public static Maybe create(MaybeOnSubscribe onSubscribe) { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe defer(final Callable> maybeSupplier) { ObjectHelper.requireNonNull(maybeSupplier, "maybeSupplier is null"); @@ -620,6 +630,7 @@ public static Maybe empty() { * @see ReactiveX operators documentation: Throw */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe error(Throwable exception) { ObjectHelper.requireNonNull(exception, "exception is null"); @@ -645,6 +656,7 @@ public static Maybe error(Throwable exception) { * @see ReactiveX operators documentation: Throw */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe error(Callable supplier) { ObjectHelper.requireNonNull(supplier, "errorSupplier is null"); @@ -657,6 +669,13 @@ public static Maybe error(Callable supplier) { *

    *
    Scheduler:
    *
    {@code fromAction} does not operate by default on a particular {@link Scheduler}.
    + *
    Error handling:
    + *
    If the {@link Action} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link MaybeObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Maybe} 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 the target type * @param run the runnable to run for each subscriber @@ -664,6 +683,7 @@ public static Maybe error(Callable supplier) { * @throws NullPointerException if run is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe fromAction(final Action run) { ObjectHelper.requireNonNull(run, "run is null"); @@ -683,6 +703,7 @@ public static Maybe fromAction(final Action run) { * @throws NullPointerException if completable is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe fromCompletable(CompletableSource completableSource) { ObjectHelper.requireNonNull(completableSource, "completableSource is null"); @@ -702,6 +723,7 @@ public static Maybe fromCompletable(CompletableSource completableSource) * @throws NullPointerException if single is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe fromSingle(SingleSource singleSource) { ObjectHelper.requireNonNull(singleSource, "singleSource is null"); @@ -743,6 +765,7 @@ public static Maybe fromSingle(SingleSource singleSource) { * @return a new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe fromCallable(@NonNull final Callable callable) { ObjectHelper.requireNonNull(callable, "callable is null"); @@ -760,7 +783,7 @@ public static Maybe fromCallable(@NonNull final Callable cal *

    * Important note: This Maybe is blocking; you cannot dispose it. *

    - * Unlike 1.x, cancelling the Maybe won't cancel the future. If necessary, one can use composition to achieve the + * Unlike 1.x, disposing the Maybe won't cancel the future. If necessary, one can use composition to achieve the * cancellation effect: {@code futureMaybe.doOnDispose(() -> future.cancel(true));}. *

    *
    Scheduler:
    @@ -776,6 +799,7 @@ public static Maybe fromCallable(@NonNull final Callable cal * @see ReactiveX operators documentation: From */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe fromFuture(Future future) { ObjectHelper.requireNonNull(future, "future is null"); @@ -791,7 +815,7 @@ public static Maybe fromFuture(Future future) { * return value of the {@link Future#get} method of that object, by passing the object into the {@code fromFuture} * method. *

    - * Unlike 1.x, cancelling the Maybe won't cancel the future. If necessary, one can use composition to achieve the + * Unlike 1.x, disposing the Maybe won't cancel the future. If necessary, one can use composition to achieve the * cancellation effect: {@code futureMaybe.doOnCancel(() -> future.cancel(true));}. *

    * Important note: This Maybe is blocking on the thread it gets subscribed on; you cannot dispose it. @@ -813,6 +837,7 @@ public static Maybe fromFuture(Future future) { * @see ReactiveX operators documentation: From */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe fromFuture(Future future, long timeout, TimeUnit unit) { ObjectHelper.requireNonNull(future, "future is null"); @@ -833,13 +858,13 @@ public static Maybe fromFuture(Future future, long timeout, * @throws NullPointerException if run is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe fromRunnable(final Runnable run) { ObjectHelper.requireNonNull(run, "run is null"); return RxJavaPlugins.onAssembly(new MaybeFromRunnable(run)); } - /** * Returns a {@code Maybe} that emits a specified item. *

    @@ -860,6 +885,7 @@ public static Maybe fromRunnable(final Runnable run) { * @see ReactiveX operators documentation: Just */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe just(T item) { ObjectHelper.requireNonNull(item, "item is null"); @@ -876,7 +902,7 @@ public static Maybe just(T item) { *

    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are cancelled. + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. @@ -910,7 +936,7 @@ public static Flowable merge(Iterable> *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are cancelled. + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. @@ -944,7 +970,7 @@ public static Flowable merge(Publisher *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are cancelled. + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. @@ -964,6 +990,7 @@ public static Flowable merge(Publisher */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) public static Flowable merge(Publisher> sources, int maxConcurrency) { @@ -996,6 +1023,7 @@ public static Flowable merge(Publisher * @see ReactiveX operators documentation: Merge */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) public static Maybe merge(MaybeSource> source) { @@ -1017,7 +1045,7 @@ public static Maybe merge(MaybeSource> *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are cancelled. + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. @@ -1041,6 +1069,7 @@ public static Maybe merge(MaybeSource> */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Flowable merge( @@ -1065,7 +1094,7 @@ public static Flowable merge( *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are cancelled. + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. @@ -1091,6 +1120,7 @@ public static Flowable merge( */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Flowable merge( @@ -1117,7 +1147,7 @@ public static Flowable merge( *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are cancelled. + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. @@ -1145,6 +1175,7 @@ public static Flowable merge( */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Flowable merge( @@ -1168,7 +1199,7 @@ public static Flowable merge( *
    {@code mergeArray} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are cancelled. + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. @@ -1187,6 +1218,7 @@ public static Flowable merge( */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Flowable mergeArray(MaybeSource... sources) { @@ -1238,7 +1270,6 @@ public static Flowable mergeArrayDelayError(MaybeSource... s return Flowable.fromArray(sources).flatMap((Function)MaybeToPublisher.instance(), true, sources.length); } - /** * Flattens an Iterable of MaybeSources into one Flowable, in a way that allows a Subscriber to receive all * successfully emitted items from each of the source MaybeSources without being interrupted by an error @@ -1274,7 +1305,6 @@ public static Flowable mergeDelayError(Iterable Flowable mergeDelayError(Publisher Flowable mergeDelayError(Publisher Flowable mergeDelayError(Publisher> sources, int maxConcurrency) { ObjectHelper.requireNonNull(sources, "source is null"); @@ -1382,6 +1412,7 @@ public static Flowable mergeDelayError(Publisher Flowable mergeDelayError(MaybeSource source1, MaybeSource source2) { ObjectHelper.requireNonNull(source1, "source1 is null"); @@ -1423,6 +1454,7 @@ public static Flowable mergeDelayError(MaybeSource source1, @SuppressWarnings({ "unchecked" }) @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Flowable mergeDelayError(MaybeSource source1, MaybeSource source2, MaybeSource source3) { @@ -1432,7 +1464,6 @@ public static Flowable mergeDelayError(MaybeSource source1, return mergeArrayDelayError(source1, source2, source3); } - /** * Flattens four MaybeSources into one Flowable, in a way that allows a Subscriber to receive all * successfully emitted items from all of the source MaybeSources without being interrupted by an error @@ -1469,6 +1500,7 @@ public static Flowable mergeDelayError(MaybeSource source1, @SuppressWarnings({ "unchecked" }) @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Flowable mergeDelayError( MaybeSource source1, MaybeSource source2, @@ -1503,7 +1535,6 @@ public static Maybe never() { return RxJavaPlugins.onAssembly((Maybe)MaybeNever.INSTANCE); } - /** * Returns a Single that emits a Boolean value that indicates whether two MaybeSource sequences are the * same by comparing the items emitted by each MaybeSource pairwise. @@ -1553,6 +1584,7 @@ public static Single sequenceEqual(MaybeSource source1 * @see ReactiveX operators documentation: SequenceEqual */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single sequenceEqual(MaybeSource source1, MaybeSource source2, BiPredicate isEqual) { @@ -1603,6 +1635,7 @@ public static Maybe timer(long delay, TimeUnit unit) { * @see ReactiveX operators documentation: Timer */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static Maybe timer(long delay, TimeUnit unit, Scheduler scheduler) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -1623,6 +1656,7 @@ public static Maybe timer(long delay, TimeUnit unit, Scheduler scheduler) * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe unsafeCreate(MaybeSource onSubscribe) { if (onSubscribe instanceof Maybe) { @@ -1689,6 +1723,7 @@ public static Maybe using(Callable resourceSupplier, * @see ReactiveX operators documentation: Using */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe using(Callable resourceSupplier, Function> sourceSupplier, @@ -1711,6 +1746,7 @@ public static Maybe using(Callable resourceSupplier, * @return the Maybe wrapper or the source cast to Maybe (if possible) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe wrap(MaybeSource source) { if (source instanceof Maybe) { @@ -1748,6 +1784,7 @@ public static Maybe wrap(MaybeSource source) { * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe zip(Iterable> sources, Function zipper) { ObjectHelper.requireNonNull(zipper, "zipper is null"); @@ -1782,6 +1819,7 @@ public static Maybe zip(Iterable> s */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe zip( MaybeSource source1, MaybeSource source2, @@ -1821,6 +1859,7 @@ public static Maybe zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe zip( MaybeSource source1, MaybeSource source2, MaybeSource source3, @@ -1864,6 +1903,7 @@ public static Maybe zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe zip( MaybeSource source1, MaybeSource source2, MaybeSource source3, @@ -1912,6 +1952,7 @@ public static Maybe zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe zip( MaybeSource source1, MaybeSource source2, MaybeSource source3, @@ -1964,6 +2005,7 @@ public static Maybe zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe zip( MaybeSource source1, MaybeSource source2, MaybeSource source3, @@ -2020,6 +2062,7 @@ public static Maybe zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe zip( MaybeSource source1, MaybeSource source2, MaybeSource source3, @@ -2081,6 +2124,7 @@ public static Maybe zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe zip( MaybeSource source1, MaybeSource source2, MaybeSource source3, @@ -2146,6 +2190,7 @@ public static Maybe zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe zip( MaybeSource source1, MaybeSource source2, MaybeSource source3, @@ -2193,6 +2238,7 @@ public static Maybe zip( * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Maybe zipArray(Function zipper, MaybeSource... sources) { @@ -2226,6 +2272,7 @@ public static Maybe zipArray(Function z */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe ambWith(MaybeSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2334,6 +2381,7 @@ public final Maybe cache() { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe cast(final Class clazz) { ObjectHelper.requireNonNull(clazz, "clazz is null"); @@ -2382,13 +2430,13 @@ public final Maybe compose(MaybeTransformer trans * @see ReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe concatMap(Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); return RxJavaPlugins.onAssembly(new MaybeFlatten(this, mapper)); } - /** * Returns a Flowable that emits the items emitted from the current MaybeSource, then the next, one after * the other, without interleaving them. @@ -2409,6 +2457,7 @@ public final Maybe concatMap(Function concatWith(MaybeSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2432,6 +2481,7 @@ public final Flowable concatWith(MaybeSource other) { * @see ReactiveX operators documentation: Contains */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single contains(final Object item) { ObjectHelper.requireNonNull(item, "item is null"); @@ -2439,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. *

    * @@ -2480,13 +2530,13 @@ public final Single count() { * @see ReactiveX operators documentation: DefaultIfEmpty */ @CheckReturnValue + @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)); } - /** * Returns a Maybe that signals the events emitted by the source Maybe shifted forward in time by a * specified delay. @@ -2530,6 +2580,7 @@ public final Maybe delay(long delay, TimeUnit unit) { * @see ReactiveX operators documentation: Delay */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Maybe delay(long delay, TimeUnit unit, Scheduler scheduler) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -2560,6 +2611,7 @@ public final Maybe delay(long delay, TimeUnit unit, Scheduler scheduler) { * @see ReactiveX operators documentation: Delay */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) public final Maybe delay(Publisher delayIndicator) { @@ -2585,6 +2637,7 @@ public final Maybe delay(Publisher delayIndicator) { */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe delaySubscription(Publisher subscriptionIndicator) { ObjectHelper.requireNonNull(subscriptionIndicator, "subscriptionIndicator is null"); @@ -2653,9 +2706,10 @@ public final Maybe delaySubscription(long delay, TimeUnit unit, Scheduler sch * @since 2.1 */ @CheckReturnValue + @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)); } @@ -2677,6 +2731,7 @@ public final Maybe doAfterSuccess(Consumer onAfterSuccess) { * @see ReactiveX operators documentation: Do */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe doAfterTerminate(Action onAfterTerminate) { return RxJavaPlugins.onAssembly(new MaybePeek(this, @@ -2701,11 +2756,12 @@ public final Maybe doAfterTerminate(Action onAfterTerminate) { *

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

    History: 2.0.1 - experimental - * @param onFinally the action called when this Maybe terminates or gets cancelled + * @param onFinally the action called when this Maybe terminates or gets disposed * @return the new Maybe instance * @since 2.1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe doFinally(Action onFinally) { ObjectHelper.requireNonNull(onFinally, "onFinally is null"); @@ -2719,11 +2775,12 @@ public final Maybe doFinally(Action onFinally) { *

    Scheduler:
    *
    {@code doOnDispose} does not operate by default on a particular {@link Scheduler}.
    *
    - * @param onDispose the action called when the subscription is cancelled (disposed) + * @param onDispose the action called when the subscription is disposed * @throws NullPointerException if onDispose is null * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe doOnDispose(Action onDispose) { return RxJavaPlugins.onAssembly(new MaybePeek(this, @@ -2751,6 +2808,7 @@ public final Maybe doOnDispose(Action onDispose) { * @see ReactiveX operators documentation: Do */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe doOnComplete(Action onComplete) { return RxJavaPlugins.onAssembly(new MaybePeek(this, @@ -2776,6 +2834,7 @@ public final Maybe doOnComplete(Action onComplete) { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe doOnError(Consumer onError) { return RxJavaPlugins.onAssembly(new MaybePeek(this, @@ -2820,6 +2879,7 @@ public final Maybe doOnEvent(BiConsumer onEvent * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe doOnSubscribe(Consumer onSubscribe) { return RxJavaPlugins.onAssembly(new MaybePeek(this, @@ -2832,6 +2892,33 @@ public final Maybe doOnSubscribe(Consumer onSubscribe) { )); } + /** + * Returns a Maybe instance that calls the given onTerminate callback + * just before this Maybe completes normally or with an exception. + *

    + * + *

    + * This differs from {@code doAfterTerminate} in that this happens before the {@code onComplete} 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} + * @return the new Maybe instance + * @see ReactiveX operators documentation: Do + * @see #doOnTerminate(Action) + * @since 2.2.7 - experimental + */ + @Experimental + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe doOnTerminate(final Action onTerminate) { + ObjectHelper.requireNonNull(onTerminate, "onTerminate is null"); + return RxJavaPlugins.onAssembly(new MaybeDoOnTerminate(this, onTerminate)); + } + /** * Calls the shared consumer with the success value sent via onSuccess for each * MaybeObserver that subscribes to the current Maybe. @@ -2845,11 +2932,12 @@ public final Maybe doOnSubscribe(Consumer onSubscribe) { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) 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) @@ -2875,6 +2963,7 @@ public final Maybe doOnSuccess(Consumer onSuccess) { * @see ReactiveX operators documentation: Filter */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe filter(Predicate predicate) { ObjectHelper.requireNonNull(predicate, "predicate is null"); @@ -2899,6 +2988,7 @@ public final Maybe filter(Predicate predicate) { * @see ReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe flatMap(Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2909,7 +2999,7 @@ public final Maybe flatMap(Function - * + * *
    *
    Scheduler:
    *
    {@code flatMap} does not operate by default on a particular {@link Scheduler}.
    @@ -2927,6 +3017,7 @@ public final Maybe flatMap(FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe flatMap( Function> onSuccessMapper, @@ -2961,6 +3052,7 @@ public final Maybe flatMap( * @see ReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe flatMap(Function> mapper, BiFunction resultSelector) { @@ -2970,8 +3062,8 @@ public final Maybe flatMap(Function * *
    @@ -2991,6 +3083,7 @@ public final Maybe flatMap(Function Flowable flattenAsFlowable(final Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2998,7 +3091,8 @@ public final Flowable flattenAsFlowable(final Function * *
    @@ -3015,6 +3109,7 @@ public final Flowable flattenAsFlowable(final FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Observable flattenAsObservable(final Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -3038,6 +3133,7 @@ public final Observable flattenAsObservable(final FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Observable flatMapObservable(Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -3065,6 +3161,7 @@ public final Observable flatMapObservable(Function Flowable flatMapPublisher(Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -3090,6 +3187,7 @@ public final Flowable flatMapPublisher(FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single flatMapSingle(final Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -3117,6 +3215,7 @@ public final Single flatMapSingle(final Function Maybe flatMapSingleElement(final Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -3140,6 +3239,7 @@ public final Maybe flatMapSingleElement(final FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable flatMapCompletable(final Function mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -3349,9 +3449,10 @@ public final Single isEmpty() { * @see #compose(MaybeTransformer) */ @CheckReturnValue + @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)); } @@ -3372,12 +3473,33 @@ public final Maybe lift(final MaybeOperator lift) * @see ReactiveX operators documentation: Map */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe map(Function mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); return RxJavaPlugins.onAssembly(new MaybeMap(this, mapper)); } + /** + * Maps the signal types of this Maybe into a {@link Notification} of the same kind + * and emits it as a single success value to downstream. + *

    + * + *

    + *
    Scheduler:
    + *
    {@code materialize} does not operate by default on a particular {@link Scheduler}.
    + *
    + * @return the new Single instance + * @since 2.2.4 - experimental + * @see Single#dematerialize(Function) + */ + @Experimental + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> materialize() { + return RxJavaPlugins.onAssembly(new MaybeMaterialize(this)); + } + /** * Flattens this and another Maybe into a single Flowable, without any transformation. *

    @@ -3399,6 +3521,7 @@ public final Maybe map(Function mapper) { */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Flowable mergeWith(MaybeSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -3424,6 +3547,7 @@ public final Flowable mergeWith(MaybeSource other) { * @see #subscribeOn */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Maybe observeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -3447,6 +3571,7 @@ public final Maybe observeOn(final Scheduler scheduler) { * @see ReactiveX operators documentation: Filter */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe ofType(final Class clazz) { ObjectHelper.requireNonNull(clazz, "clazz is null"); @@ -3467,6 +3592,7 @@ public final Maybe ofType(final Class clazz) { * @return the value returned by the convert function */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final R to(Function, R> convert) { try { @@ -3500,7 +3626,7 @@ public final Flowable toFlowable() { } /** - * Converts this Maybe into an Observable instance composing cancellation + * Converts this Maybe into an Observable instance composing disposal * through. *

    *
    Scheduler:
    @@ -3519,7 +3645,7 @@ public final Observable toObservable() { } /** - * Converts this Maybe into a Single instance composing cancellation + * Converts this Maybe into a Single instance composing disposal * through and turning an empty Maybe into a Single that emits the given * value through onSuccess. *
    @@ -3530,6 +3656,7 @@ public final Observable toObservable() { * @return the new Single instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single toSingle(T defaultValue) { ObjectHelper.requireNonNull(defaultValue, "defaultValue is null"); @@ -3537,7 +3664,7 @@ public final Single toSingle(T defaultValue) { } /** - * Converts this Maybe into a Single instance composing cancellation + * Converts this Maybe into a Single instance composing disposal * through and turning an empty Maybe into a signal of NoSuchElementException. *
    *
    Scheduler:
    @@ -3558,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) @@ -3575,9 +3702,10 @@ 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 @SchedulerSupport(SchedulerSupport.NONE) public final Maybe onErrorComplete(final Predicate predicate) { ObjectHelper.requireNonNull(predicate, "predicate is null"); @@ -3605,6 +3733,7 @@ public final Maybe onErrorComplete(final Predicate predica * @see ReactiveX operators documentation: Catch */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe onErrorResumeNext(final MaybeSource next) { ObjectHelper.requireNonNull(next, "next is null"); @@ -3631,6 +3760,7 @@ public final Maybe onErrorResumeNext(final MaybeSource next) { * @see ReactiveX operators documentation: Catch */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe onErrorResumeNext(Function> resumeFunction) { ObjectHelper.requireNonNull(resumeFunction, "resumeFunction is null"); @@ -3657,6 +3787,7 @@ public final Maybe onErrorResumeNext(FunctionReactiveX operators documentation: Catch */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe onErrorReturn(Function valueSupplier) { ObjectHelper.requireNonNull(valueSupplier, "valueSupplier is null"); @@ -3682,6 +3813,7 @@ public final Maybe onErrorReturn(Function val * @see ReactiveX operators documentation: Catch */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe onErrorReturnItem(final T item) { ObjectHelper.requireNonNull(item, "item is null"); @@ -3711,6 +3843,7 @@ public final Maybe onErrorReturnItem(final T item) { * @see ReactiveX operators documentation: Catch */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe onExceptionResumeNext(final MaybeSource next) { ObjectHelper.requireNonNull(next, "next is null"); @@ -3838,7 +3971,6 @@ public final Flowable repeatWhen(final Function, ? e return toFlowable().repeatWhen(handler); } - /** * Returns a Maybe that mirrors the source Maybe, resubscribing to it if it calls {@code onError} * (infinite retry count). @@ -3852,7 +3984,7 @@ public final Flowable repeatWhen(final Function, ? e *
    {@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 @@ -3874,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 */ @@ -3899,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 */ @@ -3916,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 */ @@ -3952,6 +4084,7 @@ public final Maybe retry(Predicate predicate) { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe retryUntil(final BooleanSupplier stop) { ObjectHelper.requireNonNull(stop, "stop is null"); @@ -4134,6 +4267,7 @@ public final Disposable subscribe(Consumer onSuccess, ConsumerReactiveX operators documentation: Subscribe */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe(Consumer onSuccess, Consumer onError, Action onComplete) { @@ -4150,7 +4284,7 @@ public final void subscribe(MaybeObserver observer) { observer = RxJavaPlugins.onSubscribe(this, observer); - ObjectHelper.requireNonNull(observer, "observer returned by the RxJavaPlugins hook is null"); + ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null MaybeObserver. Please check the handler provided to RxJavaPlugins.setOnMaybeSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); try { subscribeActual(observer); @@ -4190,6 +4324,7 @@ public final void subscribe(MaybeObserver observer) { * @see #observeOn */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Maybe subscribeOn(Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -4242,6 +4377,7 @@ public final > E subscribeWith(E observer) { * alternate MaybeSource if the source Maybe is empty. */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe switchIfEmpty(MaybeSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -4265,6 +4401,7 @@ public final Maybe switchIfEmpty(MaybeSource other) { * @since 2.2 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single switchIfEmpty(SingleSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -4290,6 +4427,7 @@ public final Single switchIfEmpty(SingleSource other) { * @see ReactiveX operators documentation: TakeUntil */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe takeUntil(MaybeSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -4319,6 +4457,7 @@ public final Maybe takeUntil(MaybeSource other) { */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe takeUntil(Publisher other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -4352,7 +4491,7 @@ public final Maybe timeout(long timeout, TimeUnit timeUnit) { /** * Returns a Maybe that mirrors the source Maybe but applies a timeout policy for each emitted * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, - * the resulting Maybe begins instead to mirror a fallback MaybeSource. + * the source MaybeSource is disposed and resulting Maybe begins instead to mirror a fallback MaybeSource. *

    * *

    @@ -4370,16 +4509,18 @@ public final Maybe timeout(long timeout, TimeUnit timeUnit) { * @see ReactiveX operators documentation: Timeout */ @CheckReturnValue + @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); } /** * Returns a Maybe that mirrors the source Maybe but applies a timeout policy for each emitted * item using a specified Scheduler. If the next item isn't emitted within the specified timeout duration - * starting from its predecessor, the resulting Maybe begins instead to mirror a fallback MaybeSource. + * starting from its predecessor, the source MaybeSource is disposed and resulting Maybe begins instead + * to mirror a fallback MaybeSource. *

    * *

    @@ -4399,6 +4540,7 @@ public final Maybe timeout(long timeout, TimeUnit timeUnit, MaybeSourceReactiveX operators documentation: Timeout */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Maybe timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler, MaybeSource fallback) { ObjectHelper.requireNonNull(fallback, "fallback is null"); @@ -4445,6 +4587,7 @@ public final Maybe timeout(long timeout, TimeUnit timeUnit, Scheduler schedul * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe timeout(MaybeSource timeoutIndicator) { ObjectHelper.requireNonNull(timeoutIndicator, "timeoutIndicator is null"); @@ -4453,7 +4596,7 @@ public final Maybe timeout(MaybeSource timeoutIndicator) { /** * If the current {@code Maybe} didn't signal an event before the {@code timeoutIndicator} {@link MaybeSource} signals, - * the current {@code Maybe} is cancelled and the {@code fallback} {@code MaybeSource} subscribed to + * the current {@code Maybe} is disposed and the {@code fallback} {@code MaybeSource} subscribed to * as a continuation. *
    *
    Scheduler:
    @@ -4466,6 +4609,7 @@ public final Maybe timeout(MaybeSource timeoutIndicator) { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe timeout(MaybeSource timeoutIndicator, MaybeSource fallback) { ObjectHelper.requireNonNull(timeoutIndicator, "timeoutIndicator is null"); @@ -4490,6 +4634,7 @@ public final Maybe timeout(MaybeSource timeoutIndicator, MaybeSource Maybe timeout(Publisher timeoutIndicator) { ObjectHelper.requireNonNull(timeoutIndicator, "timeoutIndicator is null"); @@ -4498,7 +4643,7 @@ public final Maybe timeout(Publisher timeoutIndicator) { /** * If the current {@code Maybe} didn't signal an event before the {@code timeoutIndicator} {@link Publisher} signals, - * the current {@code Maybe} is cancelled and the {@code fallback} {@code MaybeSource} subscribed to + * the current {@code Maybe} is disposed and the {@code fallback} {@code MaybeSource} subscribed to * as a continuation. *
    *
    Backpressure:
    @@ -4515,6 +4660,7 @@ public final Maybe timeout(Publisher timeoutIndicator) { */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe timeout(Publisher timeoutIndicator, MaybeSource fallback) { ObjectHelper.requireNonNull(timeoutIndicator, "timeoutIndicator is null"); @@ -4529,11 +4675,12 @@ public final Maybe timeout(Publisher timeoutIndicator, MaybeSourceScheduler: *
    {@code unsubscribeOn} calls dispose() of the upstream on the {@link Scheduler} you specify.
    *
    - * @param scheduler the target scheduler where to execute the cancellation + * @param scheduler the target scheduler where to execute the disposal * @return the new Maybe instance * @throws NullPointerException if scheduler is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Maybe unsubscribeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -4567,6 +4714,7 @@ public final Maybe unsubscribeOn(final Scheduler scheduler) { * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe zipWith(MaybeSource other, BiFunction zipper) { ObjectHelper.requireNonNull(other, "other is null"); diff --git a/src/main/java/io/reactivex/Observable.java b/src/main/java/io/reactivex/Observable.java index 2c43e0abf0..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,9 +51,9 @@ * *

    * 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 cancel + * {@code org.reactivestreams.Subscription} with {@link Disposable} as the primary means to dispose of * a flow. *

    * The {@code Observable} follows the protocol @@ -64,7 +64,7 @@ * the stream can be disposed through the {@code Disposable} instance provided to consumers through * {@code Observer.onSubscribe}. *

    - * Unlike the {@code Observable} of version 1.x, {@link #subscribe(Observer)} does not allow external cancellation + * Unlike the {@code Observable} of version 1.x, {@link #subscribe(Observer)} does not allow external disposal * of a subscription and the {@code Observer} instance is expected to expose such capability. *

    Example: *

    
    @@ -84,12 +84,12 @@
      *             System.out.println("Done!");
      *         }
      *     });
    - * 
    + *
      * Thread.sleep(500);
    - * // the sequence now can be cancelled via dispose()
    + * // the sequence can now be disposed via dispose()
      * d.dispose();
      * 
    - * + * * @param * the type of the items emitted by the Observable * @see Flowable @@ -116,6 +116,7 @@ public abstract class Observable implements ObservableSource { * @see ReactiveX operators documentation: Amb */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable amb(Iterable> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -142,6 +143,7 @@ public static Observable amb(Iterable Observable ambArray(ObservableSource... sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -289,6 +291,7 @@ public static Observable combineLatest(IterableReactiveX operators documentation: CombineLatest */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable combineLatest(Iterable> sources, Function combiner, int bufferSize) { @@ -381,6 +384,7 @@ public static Observable combineLatest(ObservableSource[] * @see ReactiveX operators documentation: CombineLatest */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable combineLatest(ObservableSource[] sources, Function combiner, int bufferSize) { @@ -426,6 +430,7 @@ public static Observable combineLatest(ObservableSource[] */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable combineLatest( ObservableSource source1, ObservableSource source2, @@ -468,6 +473,7 @@ public static Observable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable combineLatest( ObservableSource source1, ObservableSource source2, @@ -515,6 +521,7 @@ public static Observable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable combineLatest( ObservableSource source1, ObservableSource source2, @@ -566,6 +573,7 @@ public static Observable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable combineLatest( ObservableSource source1, ObservableSource source2, @@ -622,6 +630,7 @@ public static Observable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable combineLatest( ObservableSource source1, ObservableSource source2, @@ -682,6 +691,7 @@ public static Observable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable combineLatest( ObservableSource source1, ObservableSource source2, @@ -747,6 +757,7 @@ public static Observable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable combineLatest( ObservableSource source1, ObservableSource source2, @@ -816,6 +827,7 @@ public static Observable combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable combineLatest( ObservableSource source1, ObservableSource source2, @@ -962,6 +974,7 @@ public static Observable combineLatestDelayError(FunctionReactiveX operators documentation: CombineLatest */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable combineLatestDelayError(ObservableSource[] sources, Function combiner, int bufferSize) { @@ -1057,6 +1070,7 @@ public static Observable combineLatestDelayError(IterableReactiveX operators documentation: CombineLatest */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable combineLatestDelayError(Iterable> sources, Function combiner, int bufferSize) { @@ -1084,6 +1098,7 @@ public static Observable combineLatestDelayError(Iterable Observable concat(Iterable> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -1134,6 +1149,7 @@ public static Observable concat(ObservableSource Observable concat(ObservableSource> sources, int prefetch) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -1162,6 +1178,7 @@ public static Observable concat(ObservableSource Observable concat(ObservableSource source1, ObservableSource source2) { ObjectHelper.requireNonNull(source1, "source1 is null"); @@ -1192,6 +1209,7 @@ public static Observable concat(ObservableSource source1, Ob */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable concat( ObservableSource source1, ObservableSource source2, @@ -1227,6 +1245,7 @@ public static Observable concat( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable concat( ObservableSource source1, ObservableSource source2, @@ -1259,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]); } @@ -1286,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]); } @@ -1410,6 +1429,7 @@ public static Observable concatArrayEagerDelayError(int maxConcurrency, i * @return the new ObservableSource with the concatenating behavior */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable concatDelayError(Iterable> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -1455,6 +1475,7 @@ public static Observable concatDelayError(ObservableSource Observable concatDelayError(ObservableSource> sources, int prefetch, boolean tillTheEnd) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -1607,6 +1628,7 @@ public static Observable concatEager(Iterable Observable create(ObservableOnSubscribe source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -1638,6 +1660,7 @@ public static Observable create(ObservableOnSubscribe source) { * @see ReactiveX operators documentation: Defer */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable defer(Callable> supplier) { ObjectHelper.requireNonNull(supplier, "supplier is null"); @@ -1686,6 +1709,7 @@ public static Observable empty() { * @see ReactiveX operators documentation: Throw */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable error(Callable errorSupplier) { ObjectHelper.requireNonNull(errorSupplier, "errorSupplier is null"); @@ -1711,9 +1735,10 @@ public static Observable error(Callable errorSupplie * @see ReactiveX operators documentation: Throw */ @CheckReturnValue + @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)); } @@ -1735,11 +1760,12 @@ public static Observable error(final Throwable exception) { */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) + @NonNull 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]); } @@ -1757,8 +1783,14 @@ public static Observable fromArray(T... items) { *
    *
    Scheduler:
    *
    {@code fromCallable} does not operate by default on a particular {@link Scheduler}.
    + *
    Error handling:
    + *
    If the {@link Callable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link Observer#onError(Throwable)}, + * except when the downstream has disposed this {@code Observable} 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 supplier * a function, the execution of which should be deferred; {@code fromCallable} will invoke this * function only when an observer subscribes to the ObservableSource that {@code fromCallable} returns @@ -1769,6 +1801,7 @@ public static Observable fromArray(T... items) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable fromCallable(Callable supplier) { ObjectHelper.requireNonNull(supplier, "supplier is null"); @@ -1802,6 +1835,7 @@ public static Observable fromCallable(Callable supplier) { * @see ReactiveX operators documentation: From */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable fromFuture(Future future) { ObjectHelper.requireNonNull(future, "future is null"); @@ -1839,6 +1873,7 @@ public static Observable fromFuture(Future future) { * @see ReactiveX operators documentation: From */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable fromFuture(Future future, long timeout, TimeUnit unit) { ObjectHelper.requireNonNull(future, "future is null"); @@ -1880,6 +1915,7 @@ public static Observable fromFuture(Future future, long time * @see ReactiveX operators documentation: From */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static Observable fromFuture(Future future, long timeout, TimeUnit unit, Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -1915,6 +1951,7 @@ public static Observable fromFuture(Future future, long time * @see ReactiveX operators documentation: From */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static Observable fromFuture(Future future, Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -1940,6 +1977,7 @@ public static Observable fromFuture(Future future, Scheduler * @see ReactiveX operators documentation: From */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable fromIterable(Iterable source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -1947,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 @@ -1976,6 +2014,7 @@ public static Observable fromIterable(Iterable source) { */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable fromPublisher(Publisher publisher) { ObjectHelper.requireNonNull(publisher, "publisher is null"); @@ -1986,6 +2025,11 @@ public static Observable fromPublisher(Publisher publisher) * Returns a cold, synchronous and stateless generator of values. *

    * + *

    + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. *

    *
    Scheduler:
    *
    {@code generate} does not operate by default on a particular {@link Scheduler}.
    @@ -1999,9 +2043,10 @@ public static Observable fromPublisher(Publisher publisher) * @return the new Observable instance */ @CheckReturnValue + @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()); } @@ -2010,6 +2055,11 @@ public static Observable generate(final Consumer> generator) { * Returns a cold, synchronous and stateful generator of values. *

    * + *

    + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. *

    *
    Scheduler:
    *
    {@code generate} does not operate by default on a particular {@link Scheduler}.
    @@ -2025,9 +2075,10 @@ public static Observable generate(final Consumer> generator) { * @return the new Observable instance */ @CheckReturnValue + @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()); } @@ -2035,6 +2086,11 @@ public static Observable generate(Callable initialState, final BiCo * Returns a cold, synchronous and stateful generator of values. *

    * + *

    + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. *

    *
    Scheduler:
    *
    {@code generate} does not operate by default on a particular {@link Scheduler}.
    @@ -2048,16 +2104,17 @@ public static Observable generate(Callable initialState, final BiCo * {@code onComplete} to signal a value or a terminal event. Signalling multiple {@code onNext} * in a call will make the operator signal {@code IllegalStateException}. * @param disposeState the Consumer that is called with the current state when the generator - * terminates the sequence or it gets cancelled + * terminates the sequence or it gets disposed * @return the new Observable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) 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); } @@ -2065,6 +2122,11 @@ public static Observable generate( * Returns a cold, synchronous and stateful generator of values. *

    * + *

    + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. *

    *
    Scheduler:
    *
    {@code generate} does not operate by default on a particular {@link Scheduler}.
    @@ -2090,6 +2152,11 @@ public static Observable generate(Callable initialState, BiFunction * Returns a cold, synchronous and stateful generator of values. *

    * + *

    + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. *

    *
    Scheduler:
    *
    {@code generate} does not operate by default on a particular {@link Scheduler}.
    @@ -2104,15 +2171,16 @@ public static Observable generate(Callable initialState, BiFunction * the next invocation. Signalling multiple {@code onNext} * in a call will make the operator signal {@code IllegalStateException}. * @param disposeState the Consumer that is called with the current state when the generator - * terminates the sequence or it gets cancelled + * terminates the sequence or it gets disposed * @return the new Observable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) 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)); } @@ -2168,6 +2236,7 @@ public static Observable interval(long initialDelay, long period, TimeUnit * @since 1.0.12 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static Observable interval(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -2264,6 +2333,7 @@ public static Observable intervalRange(long start, long count, long initia * @return the new Observable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static Observable intervalRange(long start, long count, long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { if (count < 0) { @@ -2313,9 +2383,10 @@ public static Observable intervalRange(long start, long count, long initia * @see #fromIterable(Iterable) */ @CheckReturnValue + @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)); } @@ -2339,10 +2410,11 @@ public static Observable just(T item) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @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); } @@ -2369,11 +2441,12 @@ public static Observable just(T item1, T item2) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @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); } @@ -2402,12 +2475,13 @@ public static Observable just(T item1, T item2, T item3) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @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); } @@ -2438,13 +2512,14 @@ public static Observable just(T item1, T item2, T item3, T item4) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @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); } @@ -2477,14 +2552,15 @@ public static Observable just(T item1, T item2, T item3, T item4, T item5 */ @SuppressWarnings("unchecked") @CheckReturnValue + @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); } @@ -2519,15 +2595,16 @@ public static Observable just(T item1, T item2, T item3, T item4, T item5 */ @SuppressWarnings("unchecked") @CheckReturnValue + @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); } @@ -2564,16 +2641,17 @@ public static Observable just(T item1, T item2, T item3, T item4, T item5 */ @SuppressWarnings("unchecked") @CheckReturnValue + @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); } @@ -2612,17 +2690,18 @@ public static Observable just(T item1, T item2, T item3, T item4, T item5 */ @SuppressWarnings("unchecked") @CheckReturnValue + @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); } @@ -2663,18 +2742,19 @@ public static Observable just(T item1, T item2, T item3, T item4, T item5 */ @SuppressWarnings("unchecked") @CheckReturnValue + @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); } @@ -2692,13 +2772,13 @@ public static Observable just(T item1, T item2, T item3, T item4, T item5 *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are cancelled. + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Observable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeDelayError(Iterable, int, int)} to merge sources and terminate only when all source {@code ObservableSource}s * have completed or failed with an error. @@ -2739,13 +2819,13 @@ public static Observable merge(Iterable{@code mergeArray} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are cancelled. + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Observable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeArrayDelayError(int, int, ObservableSource...)} to merge sources and terminate only when all source {@code ObservableSource}s * have completed or failed with an error. @@ -2785,13 +2865,13 @@ public static Observable mergeArray(int maxConcurrency, int bufferSize, O *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are cancelled. + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Observable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeDelayError(Iterable)} to merge sources and terminate only when all source {@code ObservableSource}s * have completed or failed with an error. @@ -2826,13 +2906,13 @@ public static Observable merge(Iterable{@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are cancelled. + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Observable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeDelayError(Iterable, int)} to merge sources and terminate only when all source {@code ObservableSource}s * have completed or failed with an error. @@ -2871,13 +2951,13 @@ public static Observable merge(Iterable{@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are cancelled. + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Observable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeDelayError(ObservableSource)} to merge sources and terminate only when all source {@code ObservableSource}s * have completed or failed with an error. @@ -2914,13 +2994,13 @@ public static Observable merge(ObservableSource{@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are cancelled. + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Observable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeDelayError(ObservableSource, int)} to merge sources and terminate only when all source {@code ObservableSource}s * have completed or failed with an error. @@ -2961,13 +3041,13 @@ public static Observable merge(ObservableSource{@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are cancelled. + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Observable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeDelayError(ObservableSource, ObservableSource)} to merge sources and terminate only when all source {@code ObservableSource}s * have completed or failed with an error. @@ -3004,13 +3084,13 @@ public static Observable merge(ObservableSource source1, Obs *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are cancelled. + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Observable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeDelayError(ObservableSource, ObservableSource, ObservableSource)} to merge sources and terminate only when all source {@code ObservableSource}s * have completed or failed with an error. @@ -3050,13 +3130,13 @@ public static Observable merge(ObservableSource source1, Obs *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are cancelled. + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Observable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeDelayError(ObservableSource, ObservableSource, ObservableSource, ObservableSource)} to merge sources and terminate only when all source {@code ObservableSource}s * have completed or failed with an error. @@ -3101,13 +3181,13 @@ public static Observable merge( *
    {@code mergeArray} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are cancelled. + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s - * signaled by source(s) after the returned {@code Observable} has been cancelled or terminated with a + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a * (composite) error will be sent to the same global error handler. * Use {@link #mergeArrayDelayError(ObservableSource...)} to merge sources and terminate only when all source {@code ObservableSource}s * have completed or failed with an error. @@ -3902,8 +3982,8 @@ 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 - * cancellation correctly; no safeguards are provided by the Observable itself. + * to the Reactive Streams based Observable specification by handling + * disposal correctly; no safeguards are provided by the Observable itself. *
    *
    Scheduler:
    *
    {@code unsafeCreate} by default doesn't operate on any particular {@link Scheduler}.
    @@ -3915,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"); @@ -5334,15 +5413,16 @@ public final T blockingSingle(T defaultItem) { } /** - * Returns a {@link Future} representing the single value emitted by this {@code Observable}. + * Returns a {@link Future} representing the only value emitted by this {@code Observable}. + *

    + * *

    * If the {@link Observable} emits more than one item, {@link java.util.concurrent.Future} will receive an - * {@link java.lang.IllegalArgumentException}. If the {@link Observable} is empty, {@link java.util.concurrent.Future} - * will receive an {@link java.util.NoSuchElementException}. + * {@link java.lang.IndexOutOfBoundsException}. If the {@link Observable} is empty, {@link java.util.concurrent.Future} + * will receive an {@link java.util.NoSuchElementException}. The {@code Observable} source has to terminate in order + * for the returned {@code Future} to terminate as well. *

    * If the {@code Observable} may emit more than one item, use {@code Observable.toList().toFuture()}. - *

    - * *

    *
    Scheduler:
    *
    {@code toFuture} does not operate by default on a particular {@link Scheduler}.
    @@ -5429,7 +5509,6 @@ public final void blockingSubscribe(Consumer onNext, Consumeron the current thread. *

    @@ -6158,7 +6237,7 @@ public final > Observable buffer(Callable< @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Observable cache() { - return ObservableCache.from(this); + return cacheWithInitialCapacity(16); } /** @@ -6216,7 +6295,8 @@ public final Observable cache() { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Observable cacheWithInitialCapacity(int initialCapacity) { - return ObservableCache.from(this, initialCapacity); + ObjectHelper.verifyPositive(initialCapacity, "initialCapacity"); + return RxJavaPlugins.onAssembly(new ObservableCache(this, initialCapacity)); } /** @@ -7203,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) @@ -7216,6 +7295,14 @@ public final Single count() { * source ObservableSource that are followed by another item within a computed debounce duration. *

    * + *

    + * The delivery of the item happens on the thread of the first {@code onNext} or {@code onComplete} + * signal of the generated {@code ObservableSource} sequence, + * which if takes too long, a newer item may arrive from the upstream, causing the + * generated sequence to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. *

    *
    Scheduler:
    *
    This version of {@code debounce} does not operate by default on a particular {@link Scheduler}.
    @@ -7245,6 +7332,13 @@ public final Observable debounce(Function * + *

    + * Delivery of the item after the grace period happens on the {@code computation} {@code Scheduler}'s + * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the + * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. *

    *
    Scheduler:
    *
    {@code debounce} operates by default on the {@code computation} {@link Scheduler}.
    @@ -7276,6 +7370,13 @@ public final Observable debounce(long timeout, TimeUnit unit) { * will be emitted by the resulting ObservableSource. *

    * + *

    + * Delivery of the item after the grace period happens on the given {@code Scheduler}'s + * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the + * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. *

    *
    Scheduler:
    *
    You specify which {@link Scheduler} this operator will use.
    @@ -7576,10 +7677,11 @@ public final Observable delaySubscription(long delay, TimeUnit unit, Schedule *

    * When the upstream signals an {@link Notification#createOnError(Throwable) onError} or * {@link Notification#createOnComplete() onComplete} item, the - * returned Observable cancels the flow and terminates with that type of terminal event: + * returned Observable disposes of the flow and terminates with that type of terminal event: *

    
          * Observable.just(createOnNext(1), createOnComplete(), createOnNext(2))
    -     * .doOnDispose(() -> System.out.println("Cancelled!"));
    +     * .doOnDispose(() -> System.out.println("Disposed!"));
    +     * .dematerialize()
          * .test()
          * .assertResult(1);
          * 
    @@ -7587,6 +7689,7 @@ public final Observable delaySubscription(long delay, TimeUnit unit, Schedule * with the same event. *
    
          * Observable.just(createOnNext(1), createOnNext(2))
    +     * .dematerialize()
          * .test()
          * .assertResult(1, 2);
          * 
    @@ -7601,13 +7704,69 @@ public final Observable delaySubscription(long delay, TimeUnit unit, Schedule * @return an Observable that emits the items and notifications embedded in the {@link Notification} objects * emitted by the source ObservableSource * @see ReactiveX operators documentation: Dematerialize + * @see #dematerialize(Function) + * @deprecated in 2.2.4; inherently type-unsafe as it overrides the output generic type. Use {@link #dematerialize(Function)} instead. */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) + @Deprecated + @SuppressWarnings({ "unchecked", "rawtypes" }) public final Observable dematerialize() { - @SuppressWarnings("unchecked") - Observable> m = (Observable>)this; - return RxJavaPlugins.onAssembly(new ObservableDematerialize(m)); + return RxJavaPlugins.onAssembly(new ObservableDematerialize(this, Functions.identity())); + } + + /** + * Returns an Observable that reverses the effect of {@link #materialize materialize} by transforming the + * {@link Notification} objects extracted from the source items via a selector function + * into their respective {@code Observer} signal types. + *

    + * + *

    + * 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 + * limiting instance methods to a certain generic argument shape, therefore, + * a function is used to ensure the conversion remains type safe. + *

    + * When the upstream signals an {@link Notification#createOnError(Throwable) onError} or + * {@link Notification#createOnComplete() onComplete} item, the + * returned Observable disposes of the flow and terminates with that type of terminal event: + *

    
    +     * Observable.just(createOnNext(1), createOnComplete(), createOnNext(2))
    +     * .doOnDispose(() -> System.out.println("Disposed!"));
    +     * .dematerialize(notification -> notification)
    +     * .test()
    +     * .assertResult(1);
    +     * 
    + * If the upstream signals {@code onError} or {@code onComplete} directly, the flow is terminated + * with the same event. + *
    
    +     * Observable.just(createOnNext(1), createOnNext(2))
    +     * .dematerialize(notification -> notification)
    +     * .test()
    +     * .assertResult(1, 2);
    +     * 
    + * If this behavior is not desired, the completion can be suppressed by applying {@link #concatWith(ObservableSource)} + * with a {@link #never()} source. + *
    + *
    Scheduler:
    + *
    {@code dematerialize} does not operate by default on a particular {@link Scheduler}.
    + *
    + * + * @param the output value type + * @param selector function that returns the upstream item and should return a Notification to signal + * the corresponding {@code Observer} event to the downstream. + * @return an Observable that emits the items and notifications embedded in the {@link Notification} objects + * selected from the items emitted by the source ObservableSource + * @see ReactiveX operators documentation: Dematerialize + * @since 2.2.4 - experimental + */ + @Experimental + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable dematerialize(Function> selector) { + ObjectHelper.requireNonNull(selector, "selector is null"); + return RxJavaPlugins.onAssembly(new ObservableDematerialize(this, selector)); } /** @@ -7734,6 +7893,13 @@ public final Observable distinct(Function keySelector, Call *

    * Note that the operator always retains the latest item from upstream regardless of the comparison result * and uses it in the next comparison with the next upstream item. + *

    + * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. *

    *
    Scheduler:
    *
    {@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.
    @@ -7766,6 +7932,13 @@ public final Observable distinctUntilChanged() { *

    * Note that the operator always retains the latest key from upstream regardless of the comparison result * and uses it in the next comparison with the next key derived from the next upstream item. + *

    + * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. *

    *
    Scheduler:
    *
    {@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.
    @@ -7794,6 +7967,13 @@ public final Observable distinctUntilChanged(Function keySe *

    * Note that the operator always retains the latest item from upstream regardless of the comparison result * and uses it in the next comparison with the next upstream item. + *

    + * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. *

    *
    Scheduler:
    *
    {@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.
    @@ -7877,7 +8057,7 @@ public final Observable doAfterTerminate(Action onFinally) { *
    This operator supports boundary-limited synchronous or asynchronous queue-fusion.
    *
    *

    History: 2.0.1 - experimental - * @param onFinally the action called when this Observable terminates or gets cancelled + * @param onFinally the action called when this Observable terminates or gets disposed * @return the new Observable instance * @since 2.1 */ @@ -7975,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), @@ -8040,7 +8220,7 @@ public final Observable doOnError(Consumer onError) { /** * Calls the appropriate onXXX method (shared between all Observer) for the lifecycle events of - * the sequence (subscription, cancellation, requesting). + * the sequence (subscription, disposal). *

    * *

    @@ -8913,7 +9093,7 @@ public final Observable flatMapSingle(FunctionReactiveX operators documentation: Subscribe @@ -8941,7 +9121,7 @@ public final Disposable forEach(Consumer onNext) { * @param onNext * {@link Predicate} to execute for each item. * @return - * a Disposable that allows cancelling an asynchronous sequence + * a Disposable that allows disposing of an asynchronous sequence * @throws NullPointerException * if {@code onNext} is null * @see ReactiveX operators documentation: Subscribe @@ -8965,7 +9145,7 @@ public final Disposable forEachWhile(Predicate onNext) { * @param onError * {@link Consumer} to execute when an error is emitted. * @return - * a Disposable that allows cancelling an asynchronous sequence + * a Disposable that allows disposing of an asynchronous sequence * @throws NullPointerException * if {@code onNext} is null, or * if {@code onError} is null @@ -8992,7 +9172,7 @@ public final Disposable forEachWhile(Predicate onNext, Consumer lastElement() { * * @param defaultItem * the default item to emit if the source ObservableSource is empty - * @return an Observable that emits only the last item emitted by the source ObservableSource, or a default item + * @return a Single that emits only the last item emitted by the source ObservableSource, or a default item * if the source ObservableSource is empty * @see ReactiveX operators documentation: Last */ @@ -9445,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
    @@ -9572,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));
         }
     
    @@ -9613,6 +9793,7 @@ public final  Observable map(Function mapper) {
          * @return an Observable that emits items that are the result of materializing the items and notifications
          *         of the source ObservableSource
          * @see ReactiveX operators documentation: Materialize
    +     * @see #dematerialize(Function)
          */
         @CheckReturnValue
         @SchedulerSupport(SchedulerSupport.NONE)
    @@ -9720,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.
    @@ -9736,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) @@ -9748,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.
    @@ -9768,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) @@ -9780,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.
    @@ -9801,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) @@ -10893,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 */ @@ -10911,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 */ @@ -11047,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}.
    @@ -11057,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 { @@ -11316,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); } @@ -12053,7 +12252,8 @@ public final Disposable subscribe(Consumer onNext, ConsumerReactiveX operators documentation: Subscribe */ @CheckReturnValue @@ -12079,7 +12279,7 @@ public final void subscribe(Observer observer) { try { observer = RxJavaPlugins.onSubscribe(this, observer); - ObjectHelper.requireNonNull(observer, "Plugin returned null Observer"); + ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null Observer. Please change the handler provided to RxJavaPlugins.setOnObservableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); subscribeActual(observer); } catch (NullPointerException e) { // NOPMD @@ -12204,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) @@ -12233,6 +12434,7 @@ public final Observable switchMap(FunctionReactiveX operators documentation: FlatMap + * @see #switchMapDelayError(Function, int) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -12323,7 +12525,7 @@ public final Completable switchMapCompletable(@NonNull Function Observable switchMapMaybeDelayError(@NonNull FunctionReactiveX operators documentation: FlatMap + * @see #switchMapSingleDelayError(Function) * @since 2.2 */ @CheckReturnValue @@ -12446,6 +12649,7 @@ public final Observable switchMapSingle(@NonNull FunctionReactiveX operators documentation: FlatMap + * @see #switchMapSingle(Function) * @since 2.2 */ @CheckReturnValue @@ -12477,6 +12681,7 @@ public final Observable switchMapSingleDelayError(@NonNull FunctionReactiveX operators documentation: FlatMap + * @see #switchMap(Function) * @since 2.0 */ @CheckReturnValue @@ -12508,6 +12713,7 @@ public final Observable switchMapDelayError(FunctionReactiveX operators documentation: FlatMap + * @see #switchMap(Function, int) * @since 2.0 */ @CheckReturnValue @@ -12633,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)); } @@ -12938,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)); } @@ -13455,7 +13661,8 @@ public final Observable timeout(long timeout, TimeUnit timeUnit) { /** * Returns an Observable that mirrors the source ObservableSource but applies a timeout policy for each emitted * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, - * the resulting ObservableSource begins instead to mirror a fallback ObservableSource. + * the source ObservableSource is disposed and resulting ObservableSource begins instead + * to mirror a fallback ObservableSource. *

    * *

    @@ -13482,7 +13689,8 @@ public final Observable timeout(long timeout, TimeUnit timeUnit, ObservableSo /** * Returns an Observable that mirrors the source ObservableSource but applies a timeout policy for each emitted * item using a specified Scheduler. If the next item isn't emitted within the specified timeout duration - * starting from its predecessor, the resulting ObservableSource begins instead to mirror a fallback ObservableSource. + * starting from its predecessor, the source ObservableSource is disposed and resulting ObservableSource + * begins instead to mirror a fallback ObservableSource. *

    * *

    diff --git a/src/main/java/io/reactivex/Scheduler.java b/src/main/java/io/reactivex/Scheduler.java index 0f4806d4a0..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. *

    @@ -110,7 +139,6 @@ public static long clockDriftTolerance() { return CLOCK_DRIFT_TOLERANCE_NANOSECONDS; } - /** * Retrieves or creates a new {@link Scheduler.Worker} that represents sequential execution of actions. *

    @@ -132,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); } /** @@ -333,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 @@ -449,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 f75b275a90..15ef6b6c14 100644 --- a/src/main/java/io/reactivex/Single.java +++ b/src/main/java/io/reactivex/Single.java @@ -97,7 +97,7 @@ * d.dispose(); *

    *

    - * Note that by design, subscriptions via {@link #subscribe(SingleObserver)} can't be cancelled/disposed + * Note that by design, subscriptions via {@link #subscribe(SingleObserver)} can't be disposed * from the outside (hence the * {@code void} return of the {@link #subscribe(SingleObserver)} method) and it is the * responsibility of the implementor of the {@code SingleObserver} to allow this to happen. @@ -114,7 +114,7 @@ public abstract class Single implements SingleSource { /** - * Runs multiple SingleSources and signals the events of the first one that signals (cancelling + * Runs multiple SingleSources and signals the events of the first one that signals (disposing * the rest). *

    * @@ -129,6 +129,7 @@ public abstract class Single implements SingleSource { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single amb(final Iterable> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -136,7 +137,7 @@ public static Single amb(final Iterable * @@ -180,6 +181,7 @@ public static Single ambArray(final SingleSource... sources) * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @BackpressureSupport(BackpressureKind.FULL) public static Flowable concat(Iterable> sources) { @@ -201,6 +203,7 @@ public static Flowable concat(Iterable Observable concat(ObservableSource> sources) { @@ -226,6 +229,7 @@ public static Observable concat(ObservableSource Flowable concat(Publisher> sources) { @@ -251,6 +255,7 @@ public static Flowable concat(Publisher Flowable concat(PublisherReactiveX operators documentation: Concat */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -313,6 +319,7 @@ public static Flowable concat( * @see ReactiveX operators documentation: Concat */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -350,6 +357,7 @@ public static Flowable concat( * @see ReactiveX operators documentation: Concat */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -381,6 +389,7 @@ public static Flowable concat( * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -391,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. @@ -406,6 +417,7 @@ public static Flowable concatArray(SingleSource... sources) */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Flowable concatArrayEager(SingleSource... sources) { return Flowable.fromArray(sources).concatMapEager(SingleInternalHelper.toFlowable()); @@ -433,6 +445,7 @@ public static Flowable concatArrayEager(SingleSource... sour */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Flowable concatEager(Publisher> sources) { return Flowable.fromPublisher(sources).concatMapEager(SingleInternalHelper.toFlowable()); @@ -458,13 +471,14 @@ public static Flowable concatEager(Publisher Flowable concatEager(Iterable> sources) { return Flowable.fromIterable(sources).concatMapEager(SingleInternalHelper.toFlowable()); } /** - * Provides an API (via a cold Completable) that bridges the reactive world with the callback-style world. + * Provides an API (via a cold Single) that bridges the reactive world with the callback-style world. *

    * *

    @@ -500,6 +514,7 @@ public static Flowable concatEager(Iterable Single create(SingleOnSubscribe source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -521,6 +536,7 @@ public static Single create(SingleOnSubscribe source) { * @return the new Single instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single defer(final Callable> singleSupplier) { ObjectHelper.requireNonNull(singleSupplier, "singleSupplier is null"); @@ -541,6 +557,7 @@ public static Single defer(final Callable Single error(final Callable errorSupplier) { ObjectHelper.requireNonNull(errorSupplier, "errorSupplier is null"); @@ -566,9 +583,10 @@ public static Single error(final Callable errorSuppl * @see ReactiveX operators documentation: Throw */ @CheckReturnValue + @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)); } @@ -583,6 +601,13 @@ public static Single error(final Throwable exception) { *

    *
    Scheduler:
    *
    {@code fromCallable} does not operate by default on a particular {@link Scheduler}.
    + *
    Error handling:
    + *
    If the {@link Callable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link SingleObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Single} 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 callable @@ -592,6 +617,7 @@ public static Single error(final Throwable exception) { * @return a {@link Single} whose {@link SingleObserver}s' subscriptions trigger an invocation of the given function. */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single fromCallable(final Callable callable) { ObjectHelper.requireNonNull(callable, "callable is null"); @@ -731,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 @@ -756,6 +782,7 @@ public static Single fromFuture(Future future, Scheduler sch */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single fromPublisher(final Publisher publisher) { ObjectHelper.requireNonNull(publisher, "publisher is null"); @@ -779,6 +806,7 @@ public static Single fromPublisher(final Publisher publisher * @return the new Single instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single fromObservable(ObservableSource observableSource) { ObjectHelper.requireNonNull(observableSource, "observableSource is null"); @@ -806,8 +834,9 @@ public static Single fromObservable(ObservableSource observa */ @CheckReturnValue @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)); } @@ -823,7 +852,7 @@ public static Single just(final T item) { *

    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are cancelled. + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. @@ -842,6 +871,7 @@ public static Single just(final T item) { * @see #mergeDelayError(Iterable) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable merge(Iterable> sources) { @@ -860,7 +890,7 @@ public static Flowable merge(Iterable *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are cancelled. + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. @@ -879,6 +909,7 @@ public static Flowable merge(Iterable * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -910,6 +941,7 @@ public static Flowable merge(PublisherReactiveX operators documentation: Merge */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) public static Single merge(SingleSource> source) { @@ -931,7 +963,7 @@ public static Single merge(SingleSource{@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are cancelled. + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. @@ -954,6 +986,7 @@ public static Single merge(SingleSource Flowable merge( *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are cancelled. + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. @@ -1004,6 +1037,7 @@ public static Flowable merge( * @see #mergeDelayError(SingleSource, SingleSource, SingleSource) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -1031,7 +1065,7 @@ public static Flowable merge( *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    *
    Error handling:
    *
    If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting - * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are cancelled. + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the * first one's error or, depending on the concurrency of the sources, may terminate with a * {@code CompositeException} containing two or more of the various error signals. @@ -1058,6 +1092,7 @@ public static Flowable merge( * @see #mergeDelayError(SingleSource, SingleSource, SingleSource, SingleSource) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -1072,10 +1107,11 @@ public static Flowable merge( return merge(Flowable.fromArray(source1, source2, source3, source4)); } - /** * 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.
    @@ -1090,6 +1126,7 @@ public static Flowable merge( * @since 2.2 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable mergeDelayError(Iterable> sources) { @@ -1099,6 +1136,8 @@ public static Flowable mergeDelayError(Iterable + * *
    *
    Backpressure:
    *
    The returned {@code Flowable} honors the backpressure of the downstream consumer.
    @@ -1113,6 +1152,7 @@ public static Flowable mergeDelayError(Iterable 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. @@ -1148,6 +1187,7 @@ public static Flowable mergeDelayError(Publisher 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. @@ -1187,6 +1227,7 @@ public static Flowable mergeDelayError( * @since 2.2 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -1204,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. @@ -1230,6 +1271,7 @@ public static Flowable mergeDelayError( * @since 2.2 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -1300,6 +1342,7 @@ public static Single timer(long delay, TimeUnit unit) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static Single timer(final long delay, final TimeUnit unit, final Scheduler scheduler) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -1322,6 +1365,7 @@ public static Single timer(final long delay, final TimeUnit unit, final Sc * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single equals(final SingleSource first, final SingleSource second) { // NOPMD ObjectHelper.requireNonNull(first, "first is null"); @@ -1332,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}.
    @@ -1345,6 +1391,7 @@ public static Single equals(final SingleSource first, * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single unsafeCreate(SingleSource onSubscribe) { ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); @@ -1357,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}.
    @@ -1369,7 +1418,7 @@ public static Single unsafeCreate(SingleSource onSubscribe) { * to be run by the operator * @param disposer the consumer of the generated resource that is called exactly once for * that particular resource when the generated SingleSource terminates - * (successfully or with an error) or gets cancelled. + * (successfully or with an error) or gets disposed. * @return the new Single instance * @since 2.0 */ @@ -1384,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}.
    @@ -1396,7 +1447,7 @@ public static Single using(Callable resourceSupplier, * to be run by the operator * @param disposer the consumer of the generated resource that is called exactly once for * that particular resource when the generated SingleSource terminates - * (successfully or with an error) or gets cancelled. + * (successfully or with an error) or gets disposed. * @param eager * if true, the disposer is called before the terminal event is signalled * if false, the disposer is called after the terminal event is delivered to downstream @@ -1404,6 +1455,7 @@ public static Single using(Callable resourceSupplier, * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single using( final Callable resourceSupplier, @@ -1420,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}.
    @@ -1429,6 +1483,7 @@ public static Single using( * @return the Single wrapper or the source cast to Single (if possible) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single wrap(SingleSource source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -1452,7 +1507,7 @@ public static Single wrap(SingleSource source) { *

    * *

    - * If any of the SingleSources signal an error, all other SingleSources get cancelled and the + * If any of the SingleSources signal an error, all other SingleSources get disposed and the * error emitted to downstream immediately. *

    *
    Scheduler:
    @@ -1468,6 +1523,7 @@ public static Single wrap(SingleSource source) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single zip(final Iterable> sources, Function zipper) { ObjectHelper.requireNonNull(zipper, "zipper is null"); @@ -1499,6 +1555,7 @@ public static Single zip(final IterableReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Single zip( @@ -1537,6 +1594,7 @@ public static Single zip( * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Single zip( @@ -1580,6 +1638,7 @@ public static Single zip( * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Single zip( @@ -1627,6 +1686,7 @@ public static Single zip( * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Single zip( @@ -1679,6 +1739,7 @@ public static Single zip( * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Single zip( @@ -1735,6 +1796,7 @@ public static Single zip( * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Single zip( @@ -1796,6 +1858,7 @@ public static Single zip( * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Single zip( @@ -1861,6 +1924,7 @@ public static Single zip( * @see ReactiveX operators documentation: Zip */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static Single zip( @@ -1897,7 +1961,7 @@ public static Single zip( *

    * *

    - * If any of the SingleSources signal an error, all other SingleSources get cancelled and the + * If any of the SingleSources signal an error, all other SingleSources get disposed and the * error emitted to downstream immediately. *

    *
    Scheduler:
    @@ -1913,6 +1977,7 @@ public static Single zip( * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single zipArray(Function zipper, SingleSource... sources) { ObjectHelper.requireNonNull(zipper, "zipper is null"); @@ -1937,6 +2002,7 @@ public static Single zipArray(Function * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public final Single ambWith(SingleSource other) { @@ -2015,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:
    @@ -2033,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}.
    @@ -2043,6 +2112,7 @@ public final Single cache() { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single cast(final Class clazz) { ObjectHelper.requireNonNull(clazz, "clazz is null"); @@ -2161,6 +2231,7 @@ public final Single delay(final long time, final TimeUnit unit, final Schedul * @since 2.2 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Single delay(final long time, final TimeUnit unit, final Scheduler scheduler, boolean delayError) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -2171,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. *

    @@ -2183,6 +2256,7 @@ public final Single delay(final long time, final TimeUnit unit, final Schedul * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single delaySubscription(CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2192,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. *

    @@ -2205,6 +2281,7 @@ public final Single delaySubscription(CompletableSource other) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single delaySubscription(SingleSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2214,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. *

    @@ -2227,6 +2306,7 @@ public final Single delaySubscription(SingleSource other) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single delaySubscription(ObservableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2236,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). @@ -2254,6 +2336,7 @@ public final Single delaySubscription(ObservableSource other) { */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single delaySubscription(Publisher other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2262,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 @@ -2280,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 @@ -2297,6 +2384,46 @@ public final Single delaySubscription(long time, TimeUnit unit, Scheduler sch return delaySubscription(Observable.timer(time, unit, scheduler)); } + /** + * Maps the {@link Notification} success value of this Single back into normal + * {@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 + * limiting instance methods to a certain generic argument shape, therefore, + * a function is used to ensure the conversion remains type safe. + *

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

    + * Example: + *

    
    +     * Single.just(Notification.createOnNext(1))
    +     * .dematerialize(notification -> notification)
    +     * .test()
    +     * .assertResult(1);
    +     * 
    + * @param the result type + * @param selector the function called with the success item and should + * return a {@link Notification} instance. + * @return the new Maybe instance + * @since 2.2.4 - experimental + * @see #materialize() + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @Experimental + public final Maybe dematerialize(Function> selector) { + ObjectHelper.requireNonNull(selector, "selector is null"); + return RxJavaPlugins.onAssembly(new SingleDematerialize(this, selector)); + } + /** * Calls the specified consumer with the success item after this item has been emitted to the downstream. *

    @@ -2314,9 +2441,10 @@ public final Single delaySubscription(long time, TimeUnit unit, Scheduler sch * @since 2.1 */ @CheckReturnValue + @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)); } @@ -2342,6 +2470,7 @@ public final Single doAfterSuccess(Consumer onAfterSuccess) { * @since 2.1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single doAfterTerminate(Action onAfterTerminate) { ObjectHelper.requireNonNull(onAfterTerminate, "onAfterTerminate is null"); @@ -2363,11 +2492,12 @@ public final Single doAfterTerminate(Action onAfterTerminate) { *

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

    History: 2.0.1 - experimental - * @param onFinally the action called when this Single terminates or gets cancelled + * @param onFinally the action called when this Single terminates or gets disposed * @return the new Single instance * @since 2.1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single doFinally(Action onFinally) { ObjectHelper.requireNonNull(onFinally, "onFinally is null"); @@ -2389,12 +2519,40 @@ public final Single doFinally(Action onFinally) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single doOnSubscribe(final Consumer onSubscribe) { ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); return RxJavaPlugins.onAssembly(new SingleDoOnSubscribe(this, onSubscribe)); } + /** + * Returns a Single instance that calls the given onTerminate callback + * just before this Single completes normally or with an exception. + *

    + * + *

    + * 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 onSuccess} or {@code onError} + * @return the new Single instance + * @see ReactiveX operators documentation: Do + * @see #doOnTerminate(Action) + * @since 2.2.7 - experimental + */ + @Experimental + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single doOnTerminate(final Action onTerminate) { + ObjectHelper.requireNonNull(onTerminate, "onTerminate is null"); + return RxJavaPlugins.onAssembly(new SingleDoOnTerminate(this, onTerminate)); + } + /** * Calls the shared consumer with the success value sent via onSuccess for each * SingleObserver that subscribes to the current Single. @@ -2410,6 +2568,7 @@ public final Single doOnSubscribe(final Consumer onSubscr * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single doOnSuccess(final Consumer onSuccess) { ObjectHelper.requireNonNull(onSuccess, "onSuccess is null"); @@ -2419,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}.
    @@ -2428,6 +2589,7 @@ public final Single doOnSuccess(final Consumer onSuccess) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single doOnEvent(final BiConsumer onEvent) { ObjectHelper.requireNonNull(onEvent, "onEvent is null"); @@ -2449,6 +2611,7 @@ public final Single doOnEvent(final BiConsumer * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single doOnError(final Consumer onError) { ObjectHelper.requireNonNull(onError, "onError is null"); @@ -2471,6 +2634,7 @@ public final Single doOnError(final Consumer onError) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single doOnDispose(final Action onDispose) { ObjectHelper.requireNonNull(onDispose, "onDispose is null"); @@ -2495,6 +2659,7 @@ public final Single doOnDispose(final Action onDispose) { * @see ReactiveX operators documentation: Filter */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe filter(Predicate predicate) { ObjectHelper.requireNonNull(predicate, "predicate is null"); @@ -2518,6 +2683,7 @@ public final Maybe filter(Predicate predicate) { * @see ReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single flatMap(Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2541,6 +2707,7 @@ public final Single flatMap(FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe flatMapMaybe(final Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2569,6 +2736,7 @@ public final Maybe flatMapMaybe(final Function Flowable flatMapPublisher(Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2576,8 +2744,8 @@ public final Flowable flatMapPublisher(Function * *
    @@ -2597,6 +2765,7 @@ public final Flowable flatMapPublisher(Function Flowable flattenAsFlowable(final Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2604,7 +2773,8 @@ public final Flowable flattenAsFlowable(final Function * *
    @@ -2621,6 +2791,7 @@ public final Flowable flattenAsFlowable(final FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Observable flattenAsObservable(final Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2644,6 +2815,7 @@ public final Observable flattenAsObservable(final FunctionReactiveX operators documentation: FlatMap */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Observable flatMapObservable(Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2668,6 +2840,7 @@ public final Observable flatMapObservable(Function mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2677,6 +2850,8 @@ public final Completable flatMapCompletable(final Function + * *
    *
    Scheduler:
    *
    {@code blockingGet} does not operate by default on a particular {@link Scheduler}.
    @@ -2704,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 @@ -2837,9 +3014,10 @@ public final T blockingGet() { * @see #compose(SingleTransformer) */ @CheckReturnValue + @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)); } @@ -2860,15 +3038,38 @@ public final Single lift(final SingleOperator lif * @see ReactiveX operators documentation: Map */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single map(Function mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); return RxJavaPlugins.onAssembly(new SingleMap(this, mapper)); } + /** + * Maps the signal types of this Single into a {@link Notification} of the same kind + * and emits it as a single success value to downstream. + *

    + * + *

    + *
    Scheduler:
    + *
    {@code materialize} does not operate by default on a particular {@link Scheduler}.
    + *
    + * @return the new Single instance + * @since 2.2.4 - experimental + * @see #dematerialize(Function) + */ + @Experimental + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> materialize() { + return RxJavaPlugins.onAssembly(new SingleMaterialize(this)); + } + /** * 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}.
    @@ -2886,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}.
    @@ -2897,6 +3100,7 @@ public final Single contains(Object value) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single contains(final Object value, final BiPredicate comparer) { ObjectHelper.requireNonNull(value, "value is null"); @@ -2950,6 +3154,7 @@ public final Flowable mergeWith(SingleSource other) { * @see #subscribeOn */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Single observeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -2983,6 +3188,7 @@ public final Single observeOn(final Scheduler scheduler) { * @see ReactiveX operators documentation: Catch */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single onErrorReturn(final Function resumeFunction) { ObjectHelper.requireNonNull(resumeFunction, "resumeFunction is null"); @@ -3002,6 +3208,7 @@ public final Single onErrorReturn(final Function resu * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single onErrorReturnItem(final T value) { ObjectHelper.requireNonNull(value, "value is null"); @@ -3036,6 +3243,7 @@ public final Single onErrorReturnItem(final T value) { * @see ReactiveX operators documentation: Catch */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single onErrorResumeNext(final Single resumeSingleInCaseOfError) { ObjectHelper.requireNonNull(resumeSingleInCaseOfError, "resumeSingleInCaseOfError is null"); @@ -3071,6 +3279,7 @@ public final Single onErrorResumeNext(final Single resumeSingleI * @since .20 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single onErrorResumeNext( final Function> resumeFunctionInCaseOfError) { @@ -3081,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}.
    @@ -3188,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}.
    @@ -3204,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}.
    @@ -3221,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}.
    @@ -3239,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}.
    @@ -3259,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}.
    @@ -3278,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 @@ -3324,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. @@ -3343,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}.
    @@ -3357,6 +3584,7 @@ public final Disposable subscribe() { * if {@code onCallback} is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe(final BiConsumer onCallback) { ObjectHelper.requireNonNull(onCallback, "onCallback is null"); @@ -3369,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. @@ -3393,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}.
    @@ -3410,6 +3642,7 @@ public final Disposable subscribe(Consumer onSuccess) { * if {@code onError} is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe(final Consumer onSuccess, final Consumer onError) { ObjectHelper.requireNonNull(onSuccess, "onSuccess is null"); @@ -3423,11 +3656,11 @@ 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); - ObjectHelper.requireNonNull(observer, "subscriber returned by the RxJavaPlugins hook is null"); + ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null SingleObserver. Please check the handler provided to RxJavaPlugins.setOnSingleSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); try { subscribeActual(observer); @@ -3453,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);
    @@ -3498,6 +3733,7 @@ public final > E subscribeWith(E observer) {
          * @see #observeOn
          */
         @CheckReturnValue
    +    @NonNull
         @SchedulerSupport(SchedulerSupport.CUSTOM)
         public final Single subscribeOn(final Scheduler scheduler) {
             ObjectHelper.requireNonNull(scheduler, "scheduler is null");
    @@ -3509,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}.
    @@ -3522,6 +3758,7 @@ public final Single subscribeOn(final Scheduler scheduler) { * @see ReactiveX operators documentation: TakeUntil */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single takeUntil(final CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -3533,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 @@ -3553,6 +3790,7 @@ public final Single takeUntil(final CompletableSource other) { */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single takeUntil(final Publisher other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -3564,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}.
    @@ -3578,6 +3816,7 @@ public final Single takeUntil(final Publisher other) { * @see ReactiveX operators documentation: TakeUntil */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single takeUntil(final SingleSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -3587,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}.
    @@ -3605,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.
    @@ -3624,7 +3867,9 @@ 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 - * cancelled and the other SingleSource subscribed to. + * disposed and the other SingleSource subscribed to. + *

    + * *

    *
    Scheduler:
    *
    {@code timeout} subscribes to the other SingleSource on the {@link Scheduler} you specify.
    @@ -3637,6 +3882,7 @@ public final Single timeout(long timeout, TimeUnit unit, Scheduler scheduler) * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Single timeout(long timeout, TimeUnit unit, Scheduler scheduler, SingleSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -3645,7 +3891,9 @@ 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 - * cancelled and the other SingleSource subscribed to. + * disposed and the other SingleSource subscribed to. + *

    + * *

    *
    Scheduler:
    *
    {@code timeout} subscribes to the other SingleSource on @@ -3662,6 +3910,7 @@ public final Single timeout(long timeout, TimeUnit unit, Scheduler scheduler, * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.COMPUTATION) public final Single timeout(long timeout, TimeUnit unit, SingleSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -3829,17 +4078,20 @@ 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.
    *
    *

    History: 2.0.9 - experimental - * @param scheduler the target scheduler where to execute the cancellation + * @param scheduler the target scheduler where to execute the disposal * @return the new Single instance * @throws NullPointerException if scheduler is null * @since 2.2 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Single unsubscribeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -3881,6 +4133,8 @@ public final Single zipWith(SingleSource other, BiFunction + * *

    *
    Scheduler:
    *
    {@code test} does not operate by default on a particular {@link Scheduler}.
    @@ -3898,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/CompositeException.java b/src/main/java/io/reactivex/exceptions/CompositeException.java index 748b964cf7..4915688379 100644 --- a/src/main/java/io/reactivex/exceptions/CompositeException.java +++ b/src/main/java/io/reactivex/exceptions/CompositeException.java @@ -280,7 +280,7 @@ public int size() { */ /*private */Throwable getRootCause(Throwable e) { Throwable root = e.getCause(); - if (root == null || cause == root) { + if (root == null || e == root) { return e; } while (true) { diff --git a/src/main/java/io/reactivex/exceptions/OnErrorNotImplementedException.java b/src/main/java/io/reactivex/exceptions/OnErrorNotImplementedException.java index 994a5c25a5..1cfe421916 100644 --- a/src/main/java/io/reactivex/exceptions/OnErrorNotImplementedException.java +++ b/src/main/java/io/reactivex/exceptions/OnErrorNotImplementedException.java @@ -48,6 +48,6 @@ public OnErrorNotImplementedException(String message, @NonNull Throwable e) { * the {@code Throwable} to signal; if null, a NullPointerException is constructed */ public OnErrorNotImplementedException(@NonNull Throwable e) { - super(e != null ? e.getMessage() : null, e != null ? e : new NullPointerException()); + this("The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | " + e, e); } } \ No newline at end of file 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/exceptions/UndeliverableException.java b/src/main/java/io/reactivex/exceptions/UndeliverableException.java index 6f6aec0938..d3923e0cdd 100644 --- a/src/main/java/io/reactivex/exceptions/UndeliverableException.java +++ b/src/main/java/io/reactivex/exceptions/UndeliverableException.java @@ -28,6 +28,6 @@ public final class UndeliverableException extends IllegalStateException { * @param cause the cause, not null */ public UndeliverableException(Throwable cause) { - super(cause); + super("The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | " + cause, cause); } } 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/disposables/CancellableDisposable.java b/src/main/java/io/reactivex/internal/disposables/CancellableDisposable.java index e5cc4ad656..446dd6cdf6 100644 --- a/src/main/java/io/reactivex/internal/disposables/CancellableDisposable.java +++ b/src/main/java/io/reactivex/internal/disposables/CancellableDisposable.java @@ -28,7 +28,6 @@ public final class CancellableDisposable extends AtomicReference implements Disposable { - private static final long serialVersionUID = 5718521705281392066L; public CancellableDisposable(Cancellable cancellable) { diff --git a/src/main/java/io/reactivex/internal/disposables/DisposableHelper.java b/src/main/java/io/reactivex/internal/disposables/DisposableHelper.java index f9ad177399..46f13bd743 100644 --- a/src/main/java/io/reactivex/internal/disposables/DisposableHelper.java +++ b/src/main/java/io/reactivex/internal/disposables/DisposableHelper.java @@ -20,7 +20,6 @@ import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.plugins.RxJavaPlugins; - /** * Utility methods for working with Disposables atomically. */ diff --git a/src/main/java/io/reactivex/internal/disposables/EmptyDisposable.java b/src/main/java/io/reactivex/internal/disposables/EmptyDisposable.java index 1a00840549..4e90491b69 100644 --- a/src/main/java/io/reactivex/internal/disposables/EmptyDisposable.java +++ b/src/main/java/io/reactivex/internal/disposables/EmptyDisposable.java @@ -83,7 +83,6 @@ public static void error(Throwable e, MaybeObserver observer) { observer.onError(e); } - @Override public boolean offer(Object value) { throw new UnsupportedOperationException("Should not be called!"); @@ -115,5 +114,4 @@ public int requestFusion(int mode) { return mode & ASYNC; } - } diff --git a/src/main/java/io/reactivex/internal/disposables/ResettableConnectable.java b/src/main/java/io/reactivex/internal/disposables/ResettableConnectable.java new file mode 100644 index 0000000000..a111080a77 --- /dev/null +++ b/src/main/java/io/reactivex/internal/disposables/ResettableConnectable.java @@ -0,0 +1,54 @@ +/** + * 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.disposables; + +import io.reactivex.annotations.Experimental; +import io.reactivex.disposables.Disposable; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.observables.ConnectableObservable; + +/** + * Interface allowing conditional resetting of connections in {@link ConnectableObservable}s + * and {@link ConnectableFlowable}s. + * @since 2.2.2 - experimental + */ +@Experimental +public interface ResettableConnectable { + + /** + * Reset the connectable source only if the given {@link Disposable} {@code connection} instance + * is still representing a connection established by a previous {@code connect()} connection. + *

    + * For example, an immediately previous connection should reset the connectable source: + *

    
    +     * Disposable d = connectable.connect();
    +     * 
    +     * ((ResettableConnectable)connectable).resetIf(d);
    +     * 
    + * However, if the connection indicator {@code Disposable} is from a much earlier connection, + * it should not affect the current connection: + *
    
    +     * Disposable d1 = connectable.connect();
    +     * d.dispose();
    +     *
    +     * Disposable d2 = connectable.connect();
    +     *
    +     * ((ResettableConnectable)connectable).resetIf(d);
    +     * 
    +     * assertFalse(d2.isDisposed());
    +     * 
    + * @param connection the disposable received from a previous {@code connect()} call. + */ + void resetIf(Disposable connection); +} diff --git a/src/main/java/io/reactivex/internal/disposables/SequentialDisposable.java b/src/main/java/io/reactivex/internal/disposables/SequentialDisposable.java index e250bf1925..458194ab63 100644 --- a/src/main/java/io/reactivex/internal/disposables/SequentialDisposable.java +++ b/src/main/java/io/reactivex/internal/disposables/SequentialDisposable.java @@ -28,7 +28,6 @@ public final class SequentialDisposable extends AtomicReference implements Disposable { - private static final long serialVersionUID = -754898800686245608L; /** diff --git a/src/main/java/io/reactivex/internal/fuseable/ConditionalSubscriber.java b/src/main/java/io/reactivex/internal/fuseable/ConditionalSubscriber.java index 086e696571..4535b9c18d 100644 --- a/src/main/java/io/reactivex/internal/fuseable/ConditionalSubscriber.java +++ b/src/main/java/io/reactivex/internal/fuseable/ConditionalSubscriber.java @@ -16,7 +16,7 @@ import io.reactivex.FlowableSubscriber; /** - * A Subscriber with an additional onNextIf(T) method that + * A Subscriber with an additional {@link #tryOnNext(Object)} method that * tells the caller the specified value has been accepted or * not. * @@ -30,6 +30,7 @@ public interface ConditionalSubscriber extends FlowableSubscriber { * Conditionally takes the value. * @param t the value to deliver * @return true if the value has been accepted, false if the value has been rejected + * and the next value can be sent immediately */ boolean tryOnNext(T t); } diff --git a/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java b/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java index 850e9284af..f5e803807e 100644 --- a/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java +++ b/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java @@ -13,7 +13,6 @@ package io.reactivex.internal.fuseable; - /** * Represents a SimpleQueue plus the means and constants for requesting a fusion mode. * @param the value type returned by the SimpleQueue.poll() diff --git a/src/main/java/io/reactivex/internal/observers/BasicIntQueueDisposable.java b/src/main/java/io/reactivex/internal/observers/BasicIntQueueDisposable.java index 39d68c80dc..a5d2adf430 100644 --- a/src/main/java/io/reactivex/internal/observers/BasicIntQueueDisposable.java +++ b/src/main/java/io/reactivex/internal/observers/BasicIntQueueDisposable.java @@ -26,7 +26,6 @@ public abstract class BasicIntQueueDisposable extends AtomicInteger implements QueueDisposable { - private static final long serialVersionUID = -1001730202384742097L; @Override diff --git a/src/main/java/io/reactivex/internal/observers/BiConsumerSingleObserver.java b/src/main/java/io/reactivex/internal/observers/BiConsumerSingleObserver.java index e14425f70e..188c78b1b3 100644 --- a/src/main/java/io/reactivex/internal/observers/BiConsumerSingleObserver.java +++ b/src/main/java/io/reactivex/internal/observers/BiConsumerSingleObserver.java @@ -26,7 +26,6 @@ public final class BiConsumerSingleObserver extends AtomicReference implements SingleObserver, Disposable { - private static final long serialVersionUID = 4943102778943297569L; final BiConsumer onCallback; diff --git a/src/main/java/io/reactivex/internal/observers/BlockingMultiObserver.java b/src/main/java/io/reactivex/internal/observers/BlockingMultiObserver.java index d96f3efa21..2b5f5603e0 100644 --- a/src/main/java/io/reactivex/internal/observers/BlockingMultiObserver.java +++ b/src/main/java/io/reactivex/internal/observers/BlockingMultiObserver.java @@ -19,6 +19,8 @@ import io.reactivex.disposables.Disposable; import io.reactivex.internal.util.*; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + /** * A combined Observer that awaits the success or error signal via a CountDownLatch. * @param the value type @@ -148,7 +150,7 @@ public Throwable blockingGetError(long timeout, TimeUnit unit) { BlockingHelper.verifyNonBlocking(); if (!await(timeout, unit)) { dispose(); - throw ExceptionHelper.wrapOrThrow(new TimeoutException()); + throw ExceptionHelper.wrapOrThrow(new TimeoutException(timeoutMessage(timeout, unit))); } } catch (InterruptedException ex) { dispose(); diff --git a/src/main/java/io/reactivex/internal/observers/CallbackCompletableObserver.java b/src/main/java/io/reactivex/internal/observers/CallbackCompletableObserver.java index 3555751a8e..ee059c5b37 100644 --- a/src/main/java/io/reactivex/internal/observers/CallbackCompletableObserver.java +++ b/src/main/java/io/reactivex/internal/observers/CallbackCompletableObserver.java @@ -27,7 +27,6 @@ public final class CallbackCompletableObserver extends AtomicReference implements CompletableObserver, Disposable, Consumer, LambdaConsumerIntrospection { - private static final long serialVersionUID = -4361286194466301354L; final Consumer onError; diff --git a/src/main/java/io/reactivex/internal/observers/ConsumerSingleObserver.java b/src/main/java/io/reactivex/internal/observers/ConsumerSingleObserver.java index 7c3c4ad3b7..59735e9c11 100644 --- a/src/main/java/io/reactivex/internal/observers/ConsumerSingleObserver.java +++ b/src/main/java/io/reactivex/internal/observers/ConsumerSingleObserver.java @@ -28,7 +28,6 @@ public final class ConsumerSingleObserver extends AtomicReference implements SingleObserver, Disposable, LambdaConsumerIntrospection { - private static final long serialVersionUID = -7012088219455310787L; final Consumer onSuccess; diff --git a/src/main/java/io/reactivex/internal/observers/DisposableLambdaObserver.java b/src/main/java/io/reactivex/internal/observers/DisposableLambdaObserver.java index 47d9990d4f..59d7fac2bc 100644 --- a/src/main/java/io/reactivex/internal/observers/DisposableLambdaObserver.java +++ b/src/main/java/io/reactivex/internal/observers/DisposableLambdaObserver.java @@ -61,6 +61,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { if (upstream != DisposableHelper.DISPOSED) { + upstream = DisposableHelper.DISPOSED; downstream.onError(t); } else { RxJavaPlugins.onError(t); @@ -70,20 +71,24 @@ public void onError(Throwable t) { @Override public void onComplete() { if (upstream != DisposableHelper.DISPOSED) { + upstream = DisposableHelper.DISPOSED; downstream.onComplete(); } } - @Override public void dispose() { - try { - onDispose.run(); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - RxJavaPlugins.onError(e); + Disposable d = upstream; + if (d != DisposableHelper.DISPOSED) { + upstream = DisposableHelper.DISPOSED; + try { + onDispose.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + d.dispose(); } - upstream.dispose(); } @Override diff --git a/src/main/java/io/reactivex/internal/observers/EmptyCompletableObserver.java b/src/main/java/io/reactivex/internal/observers/EmptyCompletableObserver.java index 5b70ddb624..38d0f3746e 100644 --- a/src/main/java/io/reactivex/internal/observers/EmptyCompletableObserver.java +++ b/src/main/java/io/reactivex/internal/observers/EmptyCompletableObserver.java @@ -26,7 +26,6 @@ public final class EmptyCompletableObserver extends AtomicReference implements CompletableObserver, Disposable, LambdaConsumerIntrospection { - private static final long serialVersionUID = -7545121636549663526L; @Override diff --git a/src/main/java/io/reactivex/internal/observers/ForEachWhileObserver.java b/src/main/java/io/reactivex/internal/observers/ForEachWhileObserver.java index 54b8fdbd5c..22ba3f80a8 100644 --- a/src/main/java/io/reactivex/internal/observers/ForEachWhileObserver.java +++ b/src/main/java/io/reactivex/internal/observers/ForEachWhileObserver.java @@ -26,7 +26,6 @@ public final class ForEachWhileObserver extends AtomicReference implements Observer, Disposable { - private static final long serialVersionUID = -4403180040475402120L; final Predicate onNext; diff --git a/src/main/java/io/reactivex/internal/observers/FutureObserver.java b/src/main/java/io/reactivex/internal/observers/FutureObserver.java index b3de7f40b8..9b0d12140a 100644 --- a/src/main/java/io/reactivex/internal/observers/FutureObserver.java +++ b/src/main/java/io/reactivex/internal/observers/FutureObserver.java @@ -23,6 +23,8 @@ import io.reactivex.internal.util.BlockingHelper; import io.reactivex.plugins.RxJavaPlugins; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + /** * An Observer + Future that expects exactly one upstream value and provides it * via the (blocking) Future API. @@ -92,7 +94,7 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException, Execution if (getCount() != 0) { BlockingHelper.verifyNonBlocking(); if (!await(timeout, unit)) { - throw new TimeoutException(); + throw new TimeoutException(timeoutMessage(timeout, unit)); } } diff --git a/src/main/java/io/reactivex/internal/observers/FutureSingleObserver.java b/src/main/java/io/reactivex/internal/observers/FutureSingleObserver.java index fb3b096855..1ad8242b5c 100644 --- a/src/main/java/io/reactivex/internal/observers/FutureSingleObserver.java +++ b/src/main/java/io/reactivex/internal/observers/FutureSingleObserver.java @@ -22,6 +22,8 @@ import io.reactivex.internal.util.BlockingHelper; import io.reactivex.plugins.RxJavaPlugins; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + /** * An Observer + Future that expects exactly one upstream value and provides it * via the (blocking) Future API. @@ -91,7 +93,7 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException, Execution if (getCount() != 0) { BlockingHelper.verifyNonBlocking(); if (!await(timeout, unit)) { - throw new TimeoutException(); + throw new TimeoutException(timeoutMessage(timeout, unit)); } } diff --git a/src/main/java/io/reactivex/internal/observers/InnerQueuedObserver.java b/src/main/java/io/reactivex/internal/observers/InnerQueuedObserver.java index 27d800e183..3a18b38153 100644 --- a/src/main/java/io/reactivex/internal/observers/InnerQueuedObserver.java +++ b/src/main/java/io/reactivex/internal/observers/InnerQueuedObserver.java @@ -31,7 +31,6 @@ public final class InnerQueuedObserver extends AtomicReference implements Observer, Disposable { - private static final long serialVersionUID = -5417183359794346637L; final InnerQueuedObserverSupport parent; diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableAmb.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableAmb.java index cc603acbdc..7de1c648e4 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableAmb.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableAmb.java @@ -63,8 +63,6 @@ public void subscribeActual(final CompletableObserver observer) { final AtomicBoolean once = new AtomicBoolean(); - CompletableObserver inner = new Amb(once, set, observer); - for (int i = 0; i < count; i++) { CompletableSource c = sources[i]; if (set.isDisposed()) { @@ -82,7 +80,7 @@ public void subscribeActual(final CompletableObserver observer) { } // no need to have separate subscribers because inner is stateless - c.subscribe(inner); + c.subscribe(new Amb(once, set, observer)); } if (count == 0) { @@ -91,9 +89,14 @@ public void subscribeActual(final CompletableObserver observer) { } static final class Amb implements CompletableObserver { - private final AtomicBoolean once; - private final CompositeDisposable set; - private final CompletableObserver downstream; + + final AtomicBoolean once; + + final CompositeDisposable set; + + final CompletableObserver downstream; + + Disposable upstream; Amb(AtomicBoolean once, CompositeDisposable set, CompletableObserver observer) { this.once = once; @@ -104,6 +107,7 @@ static final class Amb implements CompletableObserver { @Override public void onComplete() { if (once.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); downstream.onComplete(); } @@ -112,6 +116,7 @@ public void onComplete() { @Override public void onError(Throwable e) { if (once.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); downstream.onError(e); } else { @@ -121,8 +126,8 @@ public void onError(Throwable e) { @Override public void onSubscribe(Disposable d) { + upstream = d; set.add(d); } - } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java new file mode 100644 index 0000000000..ff7b6cced5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java @@ -0,0 +1,107 @@ +/** + * 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.completable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class CompletableAndThenCompletable extends Completable { + + final CompletableSource source; + + final CompletableSource next; + + public CompletableAndThenCompletable(CompletableSource source, CompletableSource next) { + this.source = source; + this.next = next; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new SourceObserver(observer, next)); + } + + static final class SourceObserver + extends AtomicReference + implements CompletableObserver, Disposable { + + private static final long serialVersionUID = -4101678820158072998L; + + final CompletableObserver actualObserver; + + final CompletableSource next; + + SourceObserver(CompletableObserver actualObserver, CompletableSource next) { + this.actualObserver = actualObserver; + this.next = next; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + actualObserver.onSubscribe(this); + } + } + + @Override + public void onError(Throwable e) { + actualObserver.onError(e); + } + + @Override + public void onComplete() { + next.subscribe(new NextObserver(this, actualObserver)); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } + + static final class NextObserver implements CompletableObserver { + + final AtomicReference parent; + + final CompletableObserver downstream; + + NextObserver(AtomicReference parent, CompletableObserver downstream) { + this.parent = parent; + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(parent, d); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } +} 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/CompletableFromCallable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromCallable.java index 3dbd3701b5..6b7e68defb 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromCallable.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromCallable.java @@ -18,6 +18,7 @@ import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.Exceptions; +import io.reactivex.plugins.RxJavaPlugins; public final class CompletableFromCallable 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/completable/CompletableFromPublisher.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromPublisher.java index 3791931e92..ca33d582c9 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromPublisher.java @@ -53,7 +53,6 @@ public void onSubscribe(Subscription s) { } } - @Override public void onNext(T t) { // ignored 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/completable/CompletableMaterialize.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMaterialize.java new file mode 100644 index 0000000000..5eda7f6ae2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMaterialize.java @@ -0,0 +1,40 @@ +/** + * 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.completable; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.internal.operators.mixed.MaterializeSingleObserver; + +/** + * Turn the signal types of a Completable source into a single Notification of + * equal kind. + * + * @param the element type of the source + * @since 2.2.4 - experimental + */ +@Experimental +public final class CompletableMaterialize extends Single> { + + final Completable source; + + public CompletableMaterialize(Completable source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver> observer) { + source.subscribe(new MaterializeSingleObserver(observer)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableObserveOn.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableObserveOn.java index 11dbebe280..b931ed4a5d 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableObserveOn.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableObserveOn.java @@ -38,7 +38,6 @@ static final class ObserveOnCompletableObserver extends AtomicReference implements CompletableObserver, Disposable, Runnable { - private static final long serialVersionUID = 8571289934935992137L; final CompletableObserver downstream; diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletablePeek.java b/src/main/java/io/reactivex/internal/operators/completable/CompletablePeek.java index 327cee0116..02180e4e91 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletablePeek.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletablePeek.java @@ -61,7 +61,6 @@ final class CompletableObserverImplementation implements CompletableObserver, Di this.downstream = downstream; } - @Override public void onSubscribe(final Disposable d) { try { diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableResumeNext.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableResumeNext.java index 940942c943..5111d46f32 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableResumeNext.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableResumeNext.java @@ -34,8 +34,6 @@ public CompletableResumeNext(CompletableSource source, this.errorMapper = errorMapper; } - - @Override protected void subscribeActual(final CompletableObserver observer) { ResumeNextObserver parent = new ResumeNextObserver(observer, errorMapper); @@ -60,7 +58,6 @@ static final class ResumeNextObserver this.errorMapper = errorMapper; } - @Override public void onSubscribe(Disposable d) { DisposableHelper.replace(this, d); diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableTimeout.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableTimeout.java index 90d36ad103..c11daa7342 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableTimeout.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableTimeout.java @@ -20,6 +20,8 @@ import io.reactivex.disposables.*; import io.reactivex.plugins.RxJavaPlugins; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + public final class CompletableTimeout extends Completable { final CompletableSource source; @@ -104,7 +106,7 @@ public void run() { if (once.compareAndSet(false, true)) { set.clear(); if (other == null) { - downstream.onError(new TimeoutException()); + downstream.onError(new TimeoutException(timeoutMessage(timeout, unit))); } else { other.subscribe(new DisposeObserver()); } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableUsing.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableUsing.java index a114199a91..1f107f2c00 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableUsing.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableUsing.java @@ -40,7 +40,6 @@ public CompletableUsing(Callable resourceSupplier, this.eager = eager; } - @Override protected void subscribeActual(CompletableObserver observer) { R resource; @@ -89,7 +88,6 @@ static final class UsingObserver extends AtomicReference implements CompletableObserver, Disposable { - private static final long serialVersionUID = -674404550052917487L; final CompletableObserver downstream; 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 2eaaa5ffd3..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,11 +182,12 @@ public void remove() { @Override public void dispose() { SubscriptionHelper.cancel(this); + signalConsumer(); } @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(get()); + return get() == SubscriptionHelper.CANCELLED; } } } 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/FlowableAll.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAll.java index b3f6076f43..608089ace4 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAll.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAll.java @@ -47,6 +47,7 @@ static final class AllSubscriber extends DeferredScalarSubscription super(actual); this.predicate = predicate; } + @Override public void onSubscribe(Subscription s) { if (SubscriptionHelper.validate(this.upstream, s)) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAllSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAllSingle.java index b26087b840..1ec3e9460d 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAllSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAllSingle.java @@ -57,6 +57,7 @@ static final class AllSubscriber implements FlowableSubscriber, Disposable this.downstream = actual; this.predicate = predicate; } + @Override public void onSubscribe(Subscription s) { if (SubscriptionHelper.validate(this.upstream, s)) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAny.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAny.java index 5a3d7c966f..aed50107e4 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAny.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAny.java @@ -46,6 +46,7 @@ static final class AnySubscriber extends DeferredScalarSubscription super(actual); this.predicate = predicate; } + @Override public void onSubscribe(Subscription s) { if (SubscriptionHelper.validate(this.upstream, s)) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAnySingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAnySingle.java index 7c665f2ab9..0c30c11107 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAnySingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAnySingle.java @@ -56,6 +56,7 @@ static final class AnySubscriber implements FlowableSubscriber, Disposable this.downstream = actual; this.predicate = predicate; } + @Override public void onSubscribe(Subscription s) { if (SubscriptionHelper.validate(this.upstream, s)) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBuffer.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBuffer.java index bf0cda25b1..f2f940ac2e 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBuffer.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBuffer.java @@ -159,7 +159,6 @@ static final class PublisherBufferSkipSubscriber, Subscription { - private static final long serialVersionUID = -5616169793639412593L; final Subscriber downstream; @@ -286,7 +285,6 @@ public void onComplete() { } } - static final class PublisherBufferOverlappingSubscriber> extends AtomicLong implements FlowableSubscriber, Subscription, BooleanSupplier { 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 0fb2b19e6a..06736603f1 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java @@ -78,7 +78,6 @@ protected void subscribeActual(Subscriber s) { bufferSupplier, timespan, timeskip, unit, w)); } - static final class BufferExactUnboundedSubscriber> extends QueueDrainSubscriber implements Subscription, Runnable, Disposable { final Callable bufferSupplier; @@ -236,7 +235,6 @@ static final class BufferSkipBoundedSubscriber actual, Callable bufferSupplier, long timespan, long timeskip, TimeUnit unit, Worker w) { @@ -503,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 @@ -518,7 +517,6 @@ public boolean accept(Subscriber a, U v) { return true; } - @Override public void request(long n) { requested(n); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java index ad4de6050f..830b0984ac 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java @@ -19,7 +19,7 @@ import io.reactivex.*; import io.reactivex.internal.subscriptions.SubscriptionHelper; -import io.reactivex.internal.util.*; +import io.reactivex.internal.util.BackpressureHelper; import io.reactivex.plugins.RxJavaPlugins; /** @@ -28,45 +28,93 @@ * * @param the source element type */ -public final class FlowableCache extends AbstractFlowableWithUpstream { - /** The cache and replay state. */ - final CacheState state; +public final class FlowableCache extends AbstractFlowableWithUpstream +implements FlowableSubscriber { + /** + * The subscription to the source should happen at most once. + */ final AtomicBoolean once; /** - * Private constructor because state needs to be shared between the Observable body and - * the onSubscribe function. - * @param source the upstream source whose signals to cache - * @param capacityHint the capacity hint + * The number of items per cached nodes. + */ + final int capacityHint; + + /** + * The current known array of subscriber state to notify. + */ + final AtomicReference[]> subscribers; + + /** + * A shared instance of an empty array of subscribers to avoid creating + * a new empty array when all subscribers cancel. + */ + @SuppressWarnings("rawtypes") + static final CacheSubscription[] EMPTY = new CacheSubscription[0]; + /** + * A shared instance indicating the source has no more events and there + * is no need to remember subscribers anymore. + */ + @SuppressWarnings("rawtypes") + static final CacheSubscription[] TERMINATED = new CacheSubscription[0]; + + /** + * The total number of elements in the list available for reads. + */ + volatile long size; + + /** + * The starting point of the cached items. + */ + final Node head; + + /** + * The current tail of the linked structure holding the items. + */ + Node tail; + + /** + * How many items have been put into the tail node so far. + */ + int tailOffset; + + /** + * If {@link #subscribers} is {@link #TERMINATED}, this holds the terminal error if not null. + */ + Throwable error; + + /** + * True if the source has terminated. + */ + volatile boolean done; + + /** + * Constructs an empty, non-connected cache. + * @param source the source to subscribe to for the first incoming subscriber + * @param capacityHint the number of items expected (reduce allocation frequency) */ + @SuppressWarnings("unchecked") public FlowableCache(Flowable source, int capacityHint) { super(source); - this.state = new CacheState(source, capacityHint); + this.capacityHint = capacityHint; this.once = new AtomicBoolean(); + Node n = new Node(capacityHint); + this.head = n; + this.tail = n; + this.subscribers = new AtomicReference[]>(EMPTY); } @Override protected void subscribeActual(Subscriber t) { - // we can connect first because we replay everything anyway - ReplaySubscription rp = new ReplaySubscription(t, state); - t.onSubscribe(rp); - - boolean doReplay = true; - if (state.addChild(rp)) { - if (rp.requested.get() == ReplaySubscription.CANCELLED) { - state.removeChild(rp); - doReplay = false; - } - } + CacheSubscription consumer = new CacheSubscription(t, this); + t.onSubscribe(consumer); + add(consumer); - // we ensure a single connection here to save an instance field of AtomicBoolean in state. if (!once.get() && once.compareAndSet(false, true)) { - state.connect(); - } - - if (doReplay) { - rp.replay(); + source.subscribe(this); + } else { + replay(consumer); } } @@ -75,7 +123,7 @@ protected void subscribeActual(Subscriber t) { * @return true if already connected */ /* public */boolean isConnected() { - return state.isConnected; + return once.get(); } /** @@ -83,304 +131,287 @@ protected void subscribeActual(Subscriber t) { * @return true if the cache has Subscribers */ /* public */ boolean hasSubscribers() { - return state.subscribers.get().length != 0; + return subscribers.get().length != 0; } /** * Returns the number of events currently cached. * @return the number of currently cached event count */ - /* public */ int cachedEventCount() { - return state.size(); + /* public */ long cachedEventCount() { + return size; } /** - * Contains the active child subscribers and the values to replay. - * - * @param the value type of the cached items + * Atomically adds the consumer to the {@link #subscribers} copy-on-write array + * if the source has not yet terminated. + * @param consumer the consumer to add */ - static final class CacheState extends LinkedArrayList implements FlowableSubscriber { - /** The source observable to connect to. */ - final Flowable source; - /** Holds onto the subscriber connected to source. */ - final AtomicReference connection = new AtomicReference(); - /** Guarded by connection (not this). */ - final AtomicReference[]> subscribers; - /** The default empty array of subscribers. */ - @SuppressWarnings("rawtypes") - static final ReplaySubscription[] EMPTY = new ReplaySubscription[0]; - /** The default empty array of subscribers. */ - @SuppressWarnings("rawtypes") - static final ReplaySubscription[] TERMINATED = new ReplaySubscription[0]; - - /** Set to true after connection. */ - volatile boolean isConnected; - /** - * Indicates that the source has completed emitting values or the - * Observable was forcefully terminated. - */ - boolean sourceDone; + void add(CacheSubscription consumer) { + for (;;) { + CacheSubscription[] current = subscribers.get(); + if (current == TERMINATED) { + return; + } + int n = current.length; - @SuppressWarnings("unchecked") - CacheState(Flowable source, int capacityHint) { - super(capacityHint); - this.source = source; - this.subscribers = new AtomicReference[]>(EMPTY); + @SuppressWarnings("unchecked") + CacheSubscription[] next = new CacheSubscription[n + 1]; + System.arraycopy(current, 0, next, 0, n); + next[n] = consumer; + + if (subscribers.compareAndSet(current, next)) { + return; + } } - /** - * Adds a ReplaySubscription to the subscribers array atomically. - * @param p the target ReplaySubscription wrapping a downstream Subscriber with state - * @return true if the ReplaySubscription was added or false if the cache is already terminated - */ - public boolean addChild(ReplaySubscription p) { - // guarding by connection to save on allocating another object - // thus there are two distinct locks guarding the value-addition and child come-and-go - for (;;) { - ReplaySubscription[] a = subscribers.get(); - if (a == TERMINATED) { - return false; - } - int n = a.length; - @SuppressWarnings("unchecked") - ReplaySubscription[] b = new ReplaySubscription[n + 1]; - System.arraycopy(a, 0, b, 0, n); - b[n] = p; - if (subscribers.compareAndSet(a, b)) { - return true; + } + + /** + * Atomically removes the consumer from the {@link #subscribers} copy-on-write array. + * @param consumer the consumer to remove + */ + @SuppressWarnings("unchecked") + void remove(CacheSubscription consumer) { + for (;;) { + CacheSubscription[] current = subscribers.get(); + int n = current.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (current[i] == consumer) { + j = i; + break; } } + + if (j < 0) { + return; + } + CacheSubscription[] next; + + if (n == 1) { + next = EMPTY; + } else { + next = new CacheSubscription[n - 1]; + System.arraycopy(current, 0, next, 0, j); + System.arraycopy(current, j + 1, next, j, n - j - 1); + } + + if (subscribers.compareAndSet(current, next)) { + return; + } } - /** - * Removes the ReplaySubscription (if present) from the subscribers array atomically. - * @param p the target ReplaySubscription wrapping a downstream Subscriber with state - */ - @SuppressWarnings("unchecked") - public void removeChild(ReplaySubscription p) { - for (;;) { - ReplaySubscription[] a = subscribers.get(); - int n = a.length; - if (n == 0) { - return; - } - int j = -1; - for (int i = 0; i < n; i++) { - if (a[i].equals(p)) { - j = i; - break; - } - } - if (j < 0) { - return; - } + } - ReplaySubscription[] b; - if (n == 1) { - b = EMPTY; + /** + * Replays the contents of this cache to the given consumer based on its + * current state and number of items requested by it. + * @param consumer the consumer to continue replaying items to + */ + void replay(CacheSubscription consumer) { + // make sure there is only one replay going on at a time + if (consumer.getAndIncrement() != 0) { + return; + } + + // see if there were more replay request in the meantime + int missed = 1; + // read out state into locals upfront to avoid being re-read due to volatile reads + long index = consumer.index; + int offset = consumer.offset; + Node node = consumer.node; + AtomicLong requested = consumer.requested; + Subscriber downstream = consumer.downstream; + int capacity = capacityHint; + + for (;;) { + // first see if the source has terminated, read order matters! + boolean sourceDone = done; + // and if the number of items is the same as this consumer has received + boolean empty = size == index; + + // if the source is done and we have all items so far, terminate the consumer + if (sourceDone && empty) { + // release the node object to avoid leaks through retained consumers + consumer.node = null; + // if error is not null then the source failed + Throwable ex = error; + if (ex != null) { + downstream.onError(ex); } else { - b = new ReplaySubscription[n - 1]; - System.arraycopy(a, 0, b, 0, j); - System.arraycopy(a, j + 1, b, j, n - j - 1); + downstream.onComplete(); } - if (subscribers.compareAndSet(a, b)) { + return; + } + + // there are still items not sent to the consumer + if (!empty) { + // see how many items the consumer has requested in total so far + long consumerRequested = requested.get(); + // MIN_VALUE indicates a cancelled consumer, we stop replaying + if (consumerRequested == Long.MIN_VALUE) { + // release the node object to avoid leaks through retained consumers + consumer.node = null; return; } + // if the consumer has requested more and there is more, we will emit an item + if (consumerRequested != index) { + + // if the offset in the current node has reached the node capacity + if (offset == capacity) { + // switch to the subsequent node + node = node.next; + // reset the in-node offset + offset = 0; + } + + // emit the cached item + downstream.onNext(node.values[offset]); + + // move the node offset forward + offset++; + // move the total consumed item count forward + index++; + + // retry for the next item/terminal event if any + continue; + } } - } - @Override - public void onSubscribe(Subscription s) { - SubscriptionHelper.setOnce(connection, s, Long.MAX_VALUE); + // commit the changed references back + consumer.index = index; + consumer.offset = offset; + consumer.node = node; + // release the changes and see if there were more replay request in the meantime + missed = consumer.addAndGet(-missed); + if (missed == 0) { + break; + } } + } - /** - * Connects the cache to the source. - * Make sure this is called only once. - */ - public void connect() { - source.subscribe(this); - isConnected = true; + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + int tailOffset = this.tailOffset; + // if the current tail node is full, create a fresh node + if (tailOffset == capacityHint) { + Node n = new Node(tailOffset); + n.values[0] = t; + this.tailOffset = 1; + tail.next = n; + tail = n; + } else { + tail.values[tailOffset] = t; + this.tailOffset = tailOffset + 1; } - @Override - public void onNext(T t) { - if (!sourceDone) { - Object o = NotificationLite.next(t); - add(o); - for (ReplaySubscription rp : subscribers.get()) { - rp.replay(); - } - } + size++; + for (CacheSubscription consumer : subscribers.get()) { + replay(consumer); } - @SuppressWarnings("unchecked") - @Override - public void onError(Throwable e) { - if (!sourceDone) { - sourceDone = true; - Object o = NotificationLite.error(e); - add(o); - SubscriptionHelper.cancel(connection); - for (ReplaySubscription rp : subscribers.getAndSet(TERMINATED)) { - rp.replay(); - } - } else { - RxJavaPlugins.onError(e); - } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; } - @SuppressWarnings("unchecked") - @Override - public void onComplete() { - if (!sourceDone) { - sourceDone = true; - Object o = NotificationLite.complete(); - add(o); - SubscriptionHelper.cancel(connection); - for (ReplaySubscription rp : subscribers.getAndSet(TERMINATED)) { - rp.replay(); - } - } + error = t; + done = true; + for (CacheSubscription consumer : subscribers.getAndSet(TERMINATED)) { + replay(consumer); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + done = true; + for (CacheSubscription consumer : subscribers.getAndSet(TERMINATED)) { + replay(consumer); } } /** - * Keeps track of the current request amount and the replay position for a child Subscriber. - * - * @param + * Hosts the downstream consumer and its current requested and replay states. + * {@code this} holds the work-in-progress counter for the serialized replay. + * @param the value type */ - static final class ReplaySubscription - extends AtomicInteger implements Subscription { + static final class CacheSubscription extends AtomicInteger + implements Subscription { - private static final long serialVersionUID = -2557562030197141021L; - private static final long CANCELLED = Long.MIN_VALUE; - /** The actual child subscriber. */ - final Subscriber child; - /** The cache state object. */ - final CacheState state; + private static final long serialVersionUID = 6770240836423125754L; + + final Subscriber downstream; + + final FlowableCache parent; - /** - * Number of items requested and also the cancelled indicator if - * it contains {@link #CANCELLED}. - */ final AtomicLong requested; - /** - * Contains the reference to the buffer segment in replay. - * Accessed after reading state.size() and when emitting == true. - */ - Object[] currentBuffer; - /** - * Contains the index into the currentBuffer where the next value is expected. - * Accessed after reading state.size() and when emitting == true. - */ - int currentIndexInBuffer; - /** - * Contains the absolute index up until the values have been replayed so far. - */ - int index; + Node node; - /** Number of items emitted so far. */ - long emitted; + int offset; - ReplaySubscription(Subscriber child, CacheState state) { - this.child = child; - this.state = state; + long index; + + /** + * Constructs a new instance with the actual downstream consumer and + * the parent cache object. + * @param downstream the actual consumer + * @param parent the parent that holds onto the cached items + */ + CacheSubscription(Subscriber downstream, FlowableCache parent) { + this.downstream = downstream; + this.parent = parent; + this.node = parent.head; this.requested = new AtomicLong(); } + @Override public void request(long n) { if (SubscriptionHelper.validate(n)) { BackpressureHelper.addCancel(requested, n); - replay(); + parent.replay(this); } } @Override public void cancel() { - if (requested.getAndSet(CANCELLED) != CANCELLED) { - state.removeChild(this); + if (requested.getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); } } + } + + /** + * Represents a segment of the cached item list as + * part of a linked-node-list structure. + * @param the element type + */ + static final class Node { /** - * Continue replaying available values if there are requests for them. + * The array of values held by this node. */ - public void replay() { - if (getAndIncrement() != 0) { - return; - } - - int missed = 1; - final Subscriber child = this.child; - AtomicLong rq = requested; - long e = emitted; - - for (;;) { + final T[] values; - long r = rq.get(); - - if (r == CANCELLED) { - return; - } - - // read the size, if it is non-zero, we can safely read the head and - // read values up to the given absolute index - int s = state.size(); - if (s != 0) { - Object[] b = currentBuffer; - - // latch onto the very first buffer now that it is available. - if (b == null) { - b = state.head(); - currentBuffer = b; - } - final int n = b.length - 1; - int j = index; - int k = currentIndexInBuffer; - - while (j < s && e != r) { - if (rq.get() == CANCELLED) { - return; - } - if (k == n) { - b = (Object[])b[n]; - k = 0; - } - Object o = b[k]; - - if (NotificationLite.accept(o, child)) { - return; - } - - k++; - j++; - e++; - } - - if (rq.get() == CANCELLED) { - return; - } - - if (r == e) { - Object o = b[k]; - if (NotificationLite.isComplete(o)) { - child.onComplete(); - return; - } else - if (NotificationLite.isError(o)) { - child.onError(NotificationLite.getError(o)); - return; - } - } - - index = j; - currentIndexInBuffer = k; - currentBuffer = b; - } + /** + * The next node if not null. + */ + volatile Node next; - emitted = e; - missed = addAndGet(-missed); - if (missed == 0) { - break; - } - } + @SuppressWarnings("unchecked") + Node(int capacityHint) { + this.values = (T[])new Object[capacityHint]; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java index a4bc8f6bc4..c690701fcf 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java @@ -136,7 +136,6 @@ public void subscribeActual(Subscriber s) { return; } - CombineLatestCoordinator coordinator = new CombineLatestCoordinator(s, combiner, n, bufferSize, delayErrors); @@ -148,7 +147,6 @@ public void subscribeActual(Subscriber s) { static final class CombineLatestCoordinator extends BasicIntQueueSubscription { - private static final long serialVersionUID = -5082275438355852221L; final Subscriber downstream; @@ -494,7 +492,6 @@ static final class CombineLatestInnerSubscriber extends AtomicReference implements FlowableSubscriber { - private static final long serialVersionUID = -8730235182291002949L; final CombineLatestCoordinator parent; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatArray.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatArray.java index c06cffa80a..4d7cd06c5b 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatArray.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatArray.java @@ -59,6 +59,7 @@ static final class ConcatArraySubscriber extends SubscriptionArbiter implemen long produced; ConcatArraySubscriber(Publisher[] sources, boolean delayError, Subscriber downstream) { + super(false); this.downstream = downstream; this.sources = sources; this.delayError = delayError; 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 27016a1dd8..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.*; @@ -113,7 +113,7 @@ public final void onSubscribe(Subscription s) { if (s instanceof QueueSubscription) { @SuppressWarnings("unchecked") QueueSubscription f = (QueueSubscription)s; - int m = f.requestFusion(QueueSubscription.ANY); + int m = f.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); if (m == QueueSubscription.SYNC) { sourceMode = m; queue = f; @@ -173,11 +173,9 @@ public final void innerComplete() { } - static final class ConcatMapImmediate extends BaseConcatMapSubscriber { - private static final long serialVersionUID = 7898995095634264146L; final Subscriber downstream; @@ -303,7 +301,6 @@ void drain() { } } - if (p instanceof Callable) { @SuppressWarnings("unchecked") Callable callable = (Callable) p; @@ -320,7 +317,6 @@ void drain() { return; } - if (vr == null) { continue; } @@ -336,7 +332,7 @@ void drain() { continue; } else { active = true; - inner.setSubscription(new WeakScalarSubscription(vr, inner)); + inner.setSubscription(new SimpleScalarSubscription(vr, inner)); } } else { @@ -353,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(); @@ -382,7 +378,6 @@ public void cancel() { static final class ConcatMapDelayed extends BaseConcatMapSubscriber { - private static final long serialVersionUID = -2945777694260521066L; final Subscriber downstream; @@ -417,7 +412,6 @@ public void innerNext(R value) { downstream.onNext(value); } - @Override public void innerError(Throwable e) { if (errors.addThrowable(e)) { @@ -526,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) { @@ -541,7 +538,7 @@ void drain() { continue; } else { active = true; - inner.setSubscription(new WeakScalarSubscription(vr, inner)); + inner.setSubscription(new SimpleScalarSubscription(vr, inner)); } } else { active = true; @@ -570,7 +567,6 @@ static final class ConcatMapInner extends SubscriptionArbiter implements FlowableSubscriber { - private static final long serialVersionUID = 897683679971470653L; final ConcatMapSupport parent; @@ -578,6 +574,7 @@ static final class ConcatMapInner long produced; ConcatMapInner(ConcatMapSupport parent) { + super(false); this.parent = parent; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEager.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEager.java index 87ee235704..8acad8ac69 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEager.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEager.java @@ -176,7 +176,12 @@ void drainAndCancel() { } void cancelAll() { - InnerQueuedSubscriber inner; + InnerQueuedSubscriber inner = current; + current = null; + + if (inner != null) { + inner.cancel(); + } while ((inner = subscribers.poll()) != null) { inner.cancel(); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCount.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCount.java index ebe2b07024..b29e2690a1 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCount.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCount.java @@ -32,7 +32,6 @@ protected void subscribeActual(Subscriber s) { static final class CountSubscriber extends DeferredScalarSubscription implements FlowableSubscriber { - private static final long serialVersionUID = 4973004223787171406L; Subscription upstream; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java index e7d43e466b..caf6d31101 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java @@ -28,7 +28,6 @@ import io.reactivex.internal.util.*; import io.reactivex.plugins.RxJavaPlugins; - public final class FlowableCreate extends Flowable { final FlowableOnSubscribe source; @@ -352,7 +351,6 @@ public String toString() { static final class MissingEmitter extends BaseEmitter { - private static final long serialVersionUID = 3776720187248809713L; MissingEmitter(Subscriber downstream) { @@ -414,7 +412,6 @@ public final void onNext(T t) { static final class DropAsyncEmitter extends NoOverflowBaseAsyncEmitter { - private static final long serialVersionUID = 8360058422307496563L; DropAsyncEmitter(Subscriber downstream) { @@ -430,7 +427,6 @@ void onOverflow() { static final class ErrorAsyncEmitter extends NoOverflowBaseAsyncEmitter { - private static final long serialVersionUID = 338953216916120960L; ErrorAsyncEmitter(Subscriber downstream) { @@ -446,7 +442,6 @@ void onOverflow() { static final class BufferAsyncEmitter extends BaseEmitter { - private static final long serialVersionUID = 2427151001689639875L; final SpscLinkedArrayQueue queue; @@ -589,7 +584,6 @@ void drain() { static final class LatestAsyncEmitter extends BaseEmitter { - private static final long serialVersionUID = 4023437720691792495L; final AtomicReference queue; 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/FlowableDebounceTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounceTimed.java index 36ece502bd..51d365e849 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounceTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounceTimed.java @@ -125,6 +125,7 @@ public void onComplete() { if (d != null) { d.dispose(); } + @SuppressWarnings("unchecked") DebounceEmitter de = (DebounceEmitter)d; if (de != null) { @@ -174,7 +175,6 @@ static final class DebounceEmitter extends AtomicReference implem final AtomicBoolean once = new AtomicBoolean(); - DebounceEmitter(T value, long idx, DebounceTimedSubscriber parent) { this.value = value; this.idx = idx; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDefer.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDefer.java index 8a44a0019e..7ac266001f 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDefer.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDefer.java @@ -27,6 +27,7 @@ public final class FlowableDefer extends Flowable { public FlowableDefer(Callable> supplier) { this.supplier = supplier; } + @Override public void subscribeActual(Subscriber s) { Publisher pub; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOther.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOther.java index a98892a01c..ea52987d59 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOther.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOther.java @@ -12,10 +12,12 @@ */ package io.reactivex.internal.operators.flowable; +import java.util.concurrent.atomic.*; + import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.internal.subscriptions.SubscriptionArbiter; +import io.reactivex.internal.subscriptions.SubscriptionHelper; import io.reactivex.plugins.RxJavaPlugins; /** @@ -35,94 +37,105 @@ public FlowableDelaySubscriptionOther(Publisher main, Publisher @Override public void subscribeActual(final Subscriber child) { - final SubscriptionArbiter serial = new SubscriptionArbiter(); - child.onSubscribe(serial); + MainSubscriber parent = new MainSubscriber(child, main); + child.onSubscribe(parent); + other.subscribe(parent.other); + } - FlowableSubscriber otherSubscriber = new DelaySubscriber(serial, child); + static final class MainSubscriber extends AtomicLong implements FlowableSubscriber, Subscription { - other.subscribe(otherSubscriber); - } + private static final long serialVersionUID = 2259811067697317255L; + + final Subscriber downstream; - final class DelaySubscriber implements FlowableSubscriber { - final SubscriptionArbiter serial; - final Subscriber child; - boolean done; + final Publisher main; - DelaySubscriber(SubscriptionArbiter serial, Subscriber child) { - this.serial = serial; - this.child = child; + final OtherSubscriber other; + + final AtomicReference upstream; + + MainSubscriber(Subscriber downstream, Publisher main) { + this.downstream = downstream; + this.main = main; + this.other = new OtherSubscriber(); + this.upstream = new AtomicReference(); } - @Override - public void onSubscribe(final Subscription s) { - serial.setSubscription(new DelaySubscription(s)); - s.request(Long.MAX_VALUE); + void next() { + main.subscribe(this); } @Override - public void onNext(U t) { - onComplete(); + public void onNext(T t) { + downstream.onNext(t); } @Override - public void onError(Throwable e) { - if (done) { - RxJavaPlugins.onError(e); - return; - } - done = true; - child.onError(e); + public void onError(Throwable t) { + downstream.onError(t); } @Override public void onComplete() { - if (done) { - return; - } - done = true; - - main.subscribe(new OnCompleteSubscriber()); + downstream.onComplete(); } - final class DelaySubscription implements Subscription { - - final Subscription upstream; - - DelaySubscription(Subscription s) { - this.upstream = s; + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + SubscriptionHelper.deferredRequest(upstream, this, n); } + } - @Override - public void request(long n) { - // ignored - } + @Override + public void cancel() { + SubscriptionHelper.cancel(other); + SubscriptionHelper.cancel(upstream); + } - @Override - public void cancel() { - upstream.cancel(); - } + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(upstream, this, s); } - final class OnCompleteSubscriber implements FlowableSubscriber { + final class OtherSubscriber extends AtomicReference implements FlowableSubscriber { + + private static final long serialVersionUID = -3892798459447644106L; + @Override public void onSubscribe(Subscription s) { - serial.setSubscription(s); + if (SubscriptionHelper.setOnce(this, s)) { + s.request(Long.MAX_VALUE); + } } @Override - public void onNext(T t) { - child.onNext(t); + public void onNext(Object t) { + Subscription s = get(); + if (s != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + s.cancel(); + next(); + } } @Override public void onError(Throwable t) { - child.onError(t); + Subscription s = get(); + if (s != SubscriptionHelper.CANCELLED) { + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } } @Override public void onComplete() { - child.onComplete(); + Subscription s = get(); + if (s != SubscriptionHelper.CANCELLED) { + next(); + } } } - } +} } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDematerialize.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDematerialize.java index 6257f48803..5fe5211834 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDematerialize.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDematerialize.java @@ -16,29 +16,39 @@ import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.subscriptions.SubscriptionHelper; import io.reactivex.plugins.RxJavaPlugins; -public final class FlowableDematerialize extends AbstractFlowableWithUpstream, T> { +public final class FlowableDematerialize extends AbstractFlowableWithUpstream { - public FlowableDematerialize(Flowable> source) { + final Function> selector; + + public FlowableDematerialize(Flowable source, Function> selector) { super(source); + this.selector = selector; } @Override - protected void subscribeActual(Subscriber s) { - source.subscribe(new DematerializeSubscriber(s)); + protected void subscribeActual(Subscriber subscriber) { + source.subscribe(new DematerializeSubscriber(subscriber, selector)); } - static final class DematerializeSubscriber implements FlowableSubscriber>, Subscription { - final Subscriber downstream; + static final class DematerializeSubscriber implements FlowableSubscriber, Subscription { + + final Subscriber downstream; + + final Function> selector; boolean done; Subscription upstream; - DematerializeSubscriber(Subscriber downstream) { + DematerializeSubscriber(Subscriber downstream, Function> selector) { this.downstream = downstream; + this.selector = selector; } @Override @@ -50,22 +60,35 @@ public void onSubscribe(Subscription s) { } @Override - public void onNext(Notification t) { + public void onNext(T item) { if (done) { - if (t.isOnError()) { - RxJavaPlugins.onError(t.getError()); + if (item instanceof Notification) { + Notification notification = (Notification)item; + if (notification.isOnError()) { + RxJavaPlugins.onError(notification.getError()); + } } return; } - if (t.isOnError()) { + + Notification notification; + + try { + notification = ObjectHelper.requireNonNull(selector.apply(item), "The selector returned a null Notification"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); upstream.cancel(); - onError(t.getError()); + onError(ex); + return; } - else if (t.isOnComplete()) { + if (notification.isOnError()) { + upstream.cancel(); + onError(notification.getError()); + } else if (notification.isOnComplete()) { upstream.cancel(); onComplete(); } else { - downstream.onNext(t.getValue()); + downstream.onNext(notification.getValue()); } } @@ -79,6 +102,7 @@ public void onError(Throwable t) { downstream.onError(t); } + @Override public void onComplete() { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChanged.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChanged.java index f54be6360b..c1cf54658e 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChanged.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChanged.java @@ -46,7 +46,6 @@ protected void subscribeActual(Subscriber s) { static final class DistinctUntilChangedSubscriber extends BasicFuseableSubscriber implements ConditionalSubscriber { - final Function keySelector; final BiPredicate comparer; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycle.java index f0d233881d..0c979d2918 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycle.java @@ -108,13 +108,17 @@ public void request(long n) { @Override public void cancel() { - try { - onCancel.run(); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - RxJavaPlugins.onError(e); + Subscription s = upstream; + if (s != SubscriptionHelper.CANCELLED) { + upstream = SubscriptionHelper.CANCELLED; + try { + onCancel.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + s.cancel(); } - upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableError.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableError.java index b74ad1d871..dc88f01d7e 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableError.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableError.java @@ -27,6 +27,7 @@ public final class FlowableError extends Flowable { public FlowableError(Callable errorSupplier) { this.errorSupplier = errorSupplier; } + @Override public void subscribeActual(Subscriber s) { Throwable error; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFilter.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFilter.java index 30487f90d7..dd90b2c1ec 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFilter.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFilter.java @@ -102,8 +102,6 @@ public T poll() throws Exception { } } } - - } static final class FilterConditionalSubscriber extends BasicFuseableConditionalSubscriber { 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 07e2b20db3..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); @@ -461,6 +466,7 @@ void drainLoop() { if (checkTerminate()) { return; } + @SuppressWarnings("unchecked") InnerSubscriber is = (InnerSubscriber)inner[j]; @@ -629,6 +635,7 @@ static final class InnerSubscriber extends AtomicReference this.bufferSize = parent.bufferSize; this.limit = bufferSize >> 2; } + @Override public void onSubscribe(Subscription s) { if (SubscriptionHelper.setOnce(this, s)) { @@ -654,6 +661,7 @@ public void onSubscribe(Subscription s) { s.request(bufferSize); } } + @Override public void onNext(U t) { if (fusionMode != QueueSubscription.ASYNC) { @@ -662,11 +670,13 @@ public void onNext(U t) { parent.drain(); } } + @Override public void onError(Throwable t) { lazySet(SubscriptionHelper.CANCELLED); parent.innerError(this, t); } + @Override public void onComplete() { done = true; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterable.java index 29e425f58e..01398b9a29 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterable.java @@ -85,7 +85,6 @@ static final class FlattenIterableSubscriber extends BasicIntQueueSubscription implements FlowableSubscriber { - private static final long serialVersionUID = -3096000382929934955L; final Subscriber downstream; 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 462a72481f..d54fb15a21 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java @@ -28,6 +28,7 @@ public final class FlowableFromArray extends Flowable { public FlowableFromArray(T[] array) { this.array = array; } + @Override public void subscribeActual(Subscriber s) { if (s instanceof ConditionalSubscriber) { @@ -92,13 +93,11 @@ public final void request(long n) { } } - @Override public final void cancel() { cancelled = true; } - abstract void fastPath(); abstract void slowPath(long r); @@ -106,7 +105,6 @@ public final void cancel() { static final class ArraySubscription extends BaseArraySubscription { - private static final long serialVersionUID = 2587302975077663557L; final Subscriber downstream; @@ -128,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); @@ -158,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); @@ -190,7 +188,6 @@ void slowPath(long r) { static final class ArrayConditionalSubscription extends BaseArraySubscription { - private static final long serialVersionUID = 2587302975077663557L; final ConditionalSubscriber downstream; @@ -212,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); @@ -242,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/FlowableFromCallable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromCallable.java index 4920c9f206..6dcb226daa 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromCallable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromCallable.java @@ -21,12 +21,14 @@ import io.reactivex.exceptions.Exceptions; import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.subscriptions.DeferredScalarSubscription; +import io.reactivex.plugins.RxJavaPlugins; public final class FlowableFromCallable extends Flowable implements Callable { final Callable callable; public FlowableFromCallable(Callable callable) { this.callable = callable; } + @Override public void subscribeActual(Subscriber s) { DeferredScalarSubscription deferred = new DeferredScalarSubscription(s); @@ -37,7 +39,11 @@ public void subscribeActual(Subscriber s) { t = ObjectHelper.requireNonNull(callable.call(), "The callable returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.onError(ex); + if (deferred.isCancelled()) { + RxJavaPlugins.onError(ex); + } else { + s.onError(ex); + } return; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java index 9cf14837b6..e893dca7e8 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java @@ -104,7 +104,6 @@ public final T poll() { return ObjectHelper.requireNonNull(it.next(), "Iterator.next() returned a null value"); } - @Override public final boolean isEmpty() { return it == null || !it.hasNext(); @@ -128,7 +127,6 @@ public final void request(long n) { } } - @Override public final void cancel() { cancelled = true; @@ -141,7 +139,6 @@ public final void cancel() { static final class IteratorSubscription extends BaseRangeSubscription { - private static final long serialVersionUID = -6022804456014692607L; final Subscriber downstream; @@ -195,7 +192,6 @@ void fastPath() { return; } - if (!b) { if (!cancelled) { a.onComplete(); @@ -279,7 +275,6 @@ void slowPath(long r) { static final class IteratorConditionalSubscription extends BaseRangeSubscription { - private static final long serialVersionUID = -6022804456014692607L; final ConditionalSubscriber downstream; 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 14fbc74b99..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; } @@ -430,7 +429,7 @@ public boolean isEmpty() { } } - static final class EvictionAction implements Consumer> { + static final class EvictionAction implements Consumer> { final Queue> evictedGroups; @@ -439,7 +438,7 @@ static final class EvictionAction implements Consumer> } @Override - public void accept(GroupedUnicast value) { + public void accept(GroupedUnicast value) { evictedGroups.offer(value); } } @@ -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/FlowableGroupJoin.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java index d42e3e05af..7cf3adb7b8 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java @@ -89,7 +89,6 @@ interface JoinSupport { static final class GroupJoinSubscription extends AtomicInteger implements Subscription, JoinSupport { - private static final long serialVersionUID = -6071216598687999801L; final Subscriber downstream; @@ -412,7 +411,7 @@ public void dispose() { @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(get()); + return get() == SubscriptionHelper.CANCELLED; } @Override @@ -463,7 +462,7 @@ public void dispose() { @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(get()); + return get() == SubscriptionHelper.CANCELLED; } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableJoin.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableJoin.java index 5ff5c97c3f..2bd33b64a3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableJoin.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableJoin.java @@ -73,7 +73,6 @@ protected void subscribeActual(Subscriber s) { static final class JoinSubscription extends AtomicInteger implements Subscription, JoinSupport { - private static final long serialVersionUID = -6071216598687999801L; final Subscriber downstream; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMap.java index 905f3cb6cb..7b6632dcf9 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMap.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMap.java @@ -11,7 +11,6 @@ * the License for the specific language governing permissions and limitations under the License. */ - package io.reactivex.internal.operators.flowable; import org.reactivestreams.Subscriber; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapPublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapPublisher.java index ac9fee861e..3378af4b15 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapPublisher.java @@ -11,7 +11,6 @@ * the License for the specific language governing permissions and limitations under the License. */ - package io.reactivex.internal.operators.flowable; import org.reactivestreams.*; 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 d1c97b6801..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(); } } @@ -191,6 +191,7 @@ final boolean checkTerminated(boolean d, boolean empty, Subscriber a) { if (d) { if (delayError) { if (empty) { + cancelled = true; Throwable e = error; if (e != null) { a.onError(e); @@ -203,12 +204,14 @@ final boolean checkTerminated(boolean d, boolean empty, Subscriber a) { } else { Throwable e = error; if (e != null) { + cancelled = true; clear(); a.onError(e); worker.dispose(); return true; } else if (empty) { + cancelled = true; a.onComplete(); worker.dispose(); return true; @@ -314,6 +317,7 @@ void runSync() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); + cancelled = true; upstream.cancel(); a.onError(ex); worker.dispose(); @@ -324,6 +328,7 @@ void runSync() { return; } if (v == null) { + cancelled = true; a.onComplete(); worker.dispose(); return; @@ -339,6 +344,7 @@ void runSync() { } if (q.isEmpty()) { + cancelled = true; a.onComplete(); worker.dispose(); return; @@ -379,6 +385,7 @@ void runAsync() { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); + cancelled = true; upstream.cancel(); q.clear(); @@ -441,6 +448,7 @@ void runBackfused() { downstream.onNext(null); if (d) { + cancelled = true; Throwable e = error; if (e != null) { downstream.onError(e); @@ -552,6 +560,7 @@ void runSync() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); + cancelled = true; upstream.cancel(); a.onError(ex); worker.dispose(); @@ -562,6 +571,7 @@ void runSync() { return; } if (v == null) { + cancelled = true; a.onComplete(); worker.dispose(); return; @@ -577,6 +587,7 @@ void runSync() { } if (q.isEmpty()) { + cancelled = true; a.onComplete(); worker.dispose(); return; @@ -617,6 +628,7 @@ void runAsync() { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); + cancelled = true; upstream.cancel(); q.clear(); @@ -680,6 +692,7 @@ void runBackfused() { downstream.onNext(null); if (d) { + cancelled = true; Throwable e = error; if (e != null) { downstream.onError(e); 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 139e61d410..556e54b8c3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java @@ -25,12 +25,10 @@ public final class FlowableOnBackpressureError extends AbstractFlowableWithUpstream { - public FlowableOnBackpressureError(Flowable source) { super(source); } - @Override protected void subscribeActual(Subscriber s) { this.source.subscribe(new BackpressureErrorSubscriber(s)); @@ -67,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/FlowableOnErrorNext.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorNext.java index 4d5b86c880..7110d086e0 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorNext.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorNext.java @@ -58,6 +58,7 @@ static final class OnErrorNextSubscriber long produced; OnErrorNextSubscriber(Subscriber actual, Function> nextSupplier, boolean allowFatal) { + super(false); this.downstream = actual; this.nextSupplier = nextSupplier; this.allowFatal = allowFatal; 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 9bc0d63b65..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); @@ -155,7 +170,7 @@ static final class PublishSubscriber */ final AtomicBoolean shouldConnect; - final AtomicReference s = new AtomicReference(); + final AtomicReference upstream = new AtomicReference(); /** Contains either an onComplete or an onError token from upstream. */ volatile Object terminalEvent; @@ -180,7 +195,7 @@ public void dispose() { InnerSubscriber[] ps = subscribers.getAndSet(TERMINATED); if (ps != TERMINATED) { current.compareAndSet(PublishSubscriber.this, null); - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); } } } @@ -192,12 +207,12 @@ public boolean isDisposed() { @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this.s, s)) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { if (s instanceof QueueSubscription) { @SuppressWarnings("unchecked") QueueSubscription qs = (QueueSubscription) s; - int m = qs.requestFusion(QueueSubscription.ANY); + int m = qs.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); if (m == QueueSubscription.SYNC) { sourceMode = m; queue = qs; @@ -230,6 +245,7 @@ public void onNext(T t) { // loop to act on the current state serially dispatch(); } + @Override public void onError(Throwable e) { // The observer front is accessed serially as required by spec so @@ -243,6 +259,7 @@ public void onError(Throwable e) { RxJavaPlugins.onError(e); } } + @Override public void onComplete() { // The observer front is accessed serially as required by spec so @@ -482,7 +499,7 @@ void dispatch() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.get().cancel(); + upstream.get().cancel(); term = NotificationLite.error(ex); terminalEvent = term; v = null; @@ -493,7 +510,7 @@ void dispatch() { } // otherwise, just ask for a new value if (sourceMode != QueueSubscription.SYNC) { - s.get().request(1); + upstream.get().request(1); } // and retry emitting to potential new child subscribers continue; @@ -510,7 +527,7 @@ void dispatch() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.get().cancel(); + upstream.get().cancel(); term = NotificationLite.error(ex); terminalEvent = term; v = null; @@ -555,14 +572,22 @@ void dispatch() { InnerSubscriber[] freshArray = subscribers.get(); if (subscribersChanged || freshArray != ps) { ps = freshArray; + + // if we did emit at least one element, request more to replenish the queue + if (d != 0) { + if (sourceMode != QueueSubscription.SYNC) { + upstream.get().request(d); + } + } + continue outer; } } // if we did emit at least one element, request more to replenish the queue - if (d > 0) { + if (d != 0) { if (sourceMode != QueueSubscription.SYNC) { - s.get().request(d); + upstream.get().request(d); } } // if we have requests but not an empty queue after emission 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/FlowablePublishMulticast.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java index b68e4cf711..46a2dbd7e3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java @@ -205,7 +205,7 @@ public void dispose() { @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(upstream.get()); + return upstream.get() == SubscriptionHelper.CANCELLED; } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRange.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRange.java index 198721943b..d4b6b50a59 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRange.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRange.java @@ -31,6 +31,7 @@ public FlowableRange(int start, int count) { this.start = start; this.end = start + count; } + @Override public void subscribeActual(Subscriber s) { if (s instanceof ConditionalSubscriber) { @@ -94,13 +95,11 @@ public final void request(long n) { } } - @Override public final void cancel() { cancelled = true; } - abstract void fastPath(); abstract void slowPath(long r); @@ -108,7 +107,6 @@ public final void cancel() { static final class RangeSubscription extends BaseRangeSubscription { - private static final long serialVersionUID = 2587302975077663557L; final Subscriber downstream; @@ -177,7 +175,6 @@ void slowPath(long r) { static final class RangeConditionalSubscription extends BaseRangeSubscription { - private static final long serialVersionUID = 2587302975077663557L; final ConditionalSubscriber downstream; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRangeLong.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRangeLong.java index 96bc5cddb0..d641e6a285 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRangeLong.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRangeLong.java @@ -102,7 +102,6 @@ public final void cancel() { cancelled = true; } - abstract void fastPath(); abstract void slowPath(long r); @@ -178,7 +177,6 @@ void slowPath(long r) { static final class RangeConditionalSubscription extends BaseRangeSubscription { - private static final long serialVersionUID = 2587302975077663557L; final ConditionalSubscriber downstream; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceMaybe.java index 7745c047a7..569dcdccf8 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceMaybe.java @@ -138,7 +138,5 @@ public void onComplete() { downstream.onComplete(); } } - - } } 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 deec615b20..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, @@ -95,7 +94,7 @@ protected void subscribeActual(Subscriber s) { void cancel(RefConnection rc) { SequentialDisposable sd; synchronized (this) { - if (connection == null) { + if (connection == null || connection != rc) { return; } long c = rc.subscriberCount - 1; @@ -114,28 +113,58 @@ void cancel(RefConnection rc) { sd.replace(scheduler.scheduleDirect(rc, timeout, unit)); } - void terminated(RefConnection rc) { synchronized (this) { - if (connection != null) { - connection = null; - if (rc.timer != null) { - rc.timer.dispose(); + if (source instanceof FlowablePublishClassic) { + if (connection != null && connection == rc) { + connection = null; + clearTimer(rc); } - if (source instanceof Disposable) { - ((Disposable)source).dispose(); + + 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) { connection = null; + Disposable connectionObject = rc.get(); DisposableHelper.dispose(rc); if (source instanceof Disposable) { ((Disposable)source).dispose(); + } else if (source instanceof ResettableConnectable) { + if (connectionObject == null) { + rc.disconnectedEarly = true; + } else { + ((ResettableConnectable)source).resetIf(connectionObject); + } } } } @@ -154,6 +183,8 @@ static final class RefConnection extends AtomicReference boolean connected; + boolean disconnectedEarly; + RefConnection(FlowableRefCount parent) { this.parent = parent; } @@ -166,6 +197,11 @@ public void run() { @Override public void accept(Disposable t) throws Exception { DisposableHelper.replace(this, t); + synchronized (parent) { + if (disconnectedEarly) { + ((ResettableConnectable)parent.source).resetIf(t); + } + } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeat.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeat.java index fe48ac17ea..1bbdd7cd5c 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeat.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeat.java @@ -29,7 +29,7 @@ public FlowableRepeat(Flowable source, long count) { @Override public void subscribeActual(Subscriber s) { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(false); s.onSubscribe(sa); RepeatSubscriber rs = new RepeatSubscriber(s, count != Long.MAX_VALUE ? count - 1 : Long.MAX_VALUE, sa, source); @@ -64,6 +64,7 @@ public void onNext(T t) { produced++; downstream.onNext(t); } + @Override public void onError(Throwable t) { downstream.onError(t); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatUntil.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatUntil.java index e2dbd532e8..9c7057af59 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatUntil.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatUntil.java @@ -31,7 +31,7 @@ public FlowableRepeatUntil(Flowable source, BooleanSupplier until) { @Override public void subscribeActual(Subscriber s) { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(false); s.onSubscribe(sa); RepeatSubscriber rs = new RepeatSubscriber(s, until, sa, source); @@ -66,6 +66,7 @@ public void onNext(T t) { produced++; downstream.onNext(t); } + @Override public void onError(Throwable t) { downstream.onError(t); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatWhen.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatWhen.java index ce137d4f52..b62254185d 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatWhen.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatWhen.java @@ -93,7 +93,7 @@ public void onSubscribe(Subscription s) { public void onNext(Object t) { if (getAndIncrement() == 0) { for (;;) { - if (SubscriptionHelper.isCancelled(upstream.get())) { + if (upstream.get() == SubscriptionHelper.CANCELLED) { return; } @@ -143,6 +143,7 @@ abstract static class WhenSourceSubscriber extends SubscriptionArbiter imp WhenSourceSubscriber(Subscriber actual, FlowableProcessor processor, Subscription receiver) { + super(false); this.downstream = actual; this.processor = processor; this.receiver = receiver; @@ -160,6 +161,7 @@ public final void onNext(T t) { } protected final void again(U signal) { + setSubscription(EmptySubscription.INSTANCE); long p = produced; if (p != 0L) { produced = 0L; @@ -178,7 +180,6 @@ public final void cancel() { static final class RepeatWhenSubscriber extends WhenSourceSubscriber { - private static final long serialVersionUID = -2680129890138081029L; RepeatWhenSubscriber(Subscriber actual, FlowableProcessor processor, 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 7a837f5809..196596f1b2 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java @@ -24,6 +24,7 @@ import io.reactivex.exceptions.Exceptions; import io.reactivex.flowables.ConnectableFlowable; import io.reactivex.functions.*; +import io.reactivex.internal.disposables.ResettableConnectable; import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.fuseable.HasUpstreamPublisher; import io.reactivex.internal.subscribers.SubscriberResourceWrapper; @@ -32,7 +33,7 @@ import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Timed; -public final class FlowableReplay extends ConnectableFlowable implements HasUpstreamPublisher, Disposable { +public final class FlowableReplay extends ConnectableFlowable implements HasUpstreamPublisher, ResettableConnectable { /** The source observable. */ final Flowable source; /** Holds the current subscriber that is, will be or just was subscribed to the source observable. */ @@ -161,15 +162,10 @@ protected void subscribeActual(Subscriber s) { onSubscribe.subscribe(s); } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override - public void dispose() { - current.lazySet(null); - } - - @Override - public boolean isDisposed() { - Disposable d = current.get(); - return d == null || d.isDisposed(); + public void resetIf(Disposable connectionObject) { + current.compareAndSet((ReplaySubscriber)connectionObject, null); } @Override @@ -409,6 +405,7 @@ public void onError(Throwable e) { RxJavaPlugins.onError(e); } } + @SuppressWarnings("unchecked") @Override public void onComplete() { @@ -569,6 +566,8 @@ public void dispose() { // the others had non-zero. By removing this 'blocking' child, the others // are now free to receive events parent.manageRequests(); + // make sure the last known node is not retained + index = null; } } /** @@ -624,6 +623,7 @@ static final class UnboundedReplayBuffer extends ArrayList implements UnboundedReplayBuffer(int capacityHint) { super(capacityHint); } + @Override public void next(T value) { add(NotificationLite.next(value)); @@ -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. @@ -826,6 +831,7 @@ public final void replay(InnerSubscription output) { } for (;;) { if (output.isDisposed()) { + output.index = null; return; } @@ -866,6 +872,7 @@ public final void replay(InnerSubscription output) { break; } if (output.isDisposed()) { + output.index = null; return; } } @@ -1013,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; @@ -1037,6 +1044,7 @@ void truncate() { setFirst(prev); } } + @Override void truncateFinal() { long timeLimit = scheduler.now(unit) - maxAge; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryBiPredicate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryBiPredicate.java index a049d76d51..8bc9ba27d0 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryBiPredicate.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryBiPredicate.java @@ -33,7 +33,7 @@ public FlowableRetryBiPredicate( @Override public void subscribeActual(Subscriber s) { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(false); s.onSubscribe(sa); RetryBiSubscriber rs = new RetryBiSubscriber(s, predicate, sa, source); @@ -70,6 +70,7 @@ public void onNext(T t) { produced++; downstream.onNext(t); } + @Override public void onError(Throwable t) { boolean b; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryPredicate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryPredicate.java index b29b97b70a..8d035aaf95 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryPredicate.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryPredicate.java @@ -35,7 +35,7 @@ public FlowableRetryPredicate(Flowable source, @Override public void subscribeActual(Subscriber s) { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(false); s.onSubscribe(sa); RetrySubscriber rs = new RetrySubscriber(s, count, predicate, sa, source); @@ -73,6 +73,7 @@ public void onNext(T t) { produced++; downstream.onNext(t); } + @Override public void onError(Throwable t) { long r = remaining; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryWhen.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryWhen.java index 4cf535cd90..de0f735802 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryWhen.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryWhen.java @@ -64,7 +64,6 @@ public void subscribeActual(Subscriber s) { static final class RetryWhenSubscriber extends WhenSourceSubscriber { - private static final long serialVersionUID = -2680129890138081029L; RetryWhenSubscriber(Subscriber actual, FlowableProcessor processor, 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/FlowableSequenceEqualSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java index c37bd0bac9..bcda903c85 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java @@ -98,7 +98,7 @@ public void dispose() { @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(first.get()); + return first.get() == SubscriptionHelper.CANCELLED; } void cancelAndClear() { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmpty.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmpty.java index f9fc7ebc4c..be8febdf17 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmpty.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmpty.java @@ -43,7 +43,7 @@ static final class SwitchIfEmptySubscriber implements FlowableSubscriber { this.downstream = actual; this.other = other; this.empty = true; - this.arbiter = new SubscriptionArbiter(); + this.arbiter = new SubscriptionArbiter(false); } @Override 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 0b00c03c9a..caf8c235c4 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java @@ -57,7 +57,6 @@ static final class SwitchMapSubscriber extends AtomicInteger implements Fl final int bufferSize; final boolean delayErrors; - volatile boolean done; final AtomicThrowable error; @@ -200,7 +199,6 @@ void drain() { for (;;) { if (cancelled) { - active.lazySet(null); return; } @@ -315,7 +313,7 @@ void drain() { if (r != Long.MAX_VALUE) { requested.addAndGet(-e); } - inner.get().request(e); + inner.request(e); } } @@ -399,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(); @@ -419,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/FlowableTake.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTake.java index badf1d8146..5d33c14523 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTake.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTake.java @@ -52,6 +52,7 @@ static final class TakeSubscriber extends AtomicBoolean implements FlowableSu this.limit = limit; this.remaining = limit; } + @Override public void onSubscribe(Subscription s) { if (SubscriptionHelper.validate(this.upstream, s)) { @@ -65,6 +66,7 @@ public void onSubscribe(Subscription s) { } } } + @Override public void onNext(T t) { if (!done && remaining-- > 0) { @@ -76,6 +78,7 @@ public void onNext(T t) { } } } + @Override public void onError(Throwable t) { if (!done) { @@ -86,6 +89,7 @@ public void onError(Throwable t) { RxJavaPlugins.onError(t); } } + @Override public void onComplete() { if (!done) { @@ -93,6 +97,7 @@ public void onComplete() { downstream.onComplete(); } } + @Override public void request(long n) { if (!SubscriptionHelper.validate(n)) { @@ -106,6 +111,7 @@ public void request(long n) { } upstream.request(n); } + @Override public void cancel() { upstream.cancel(); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTimed.java index 727bd4d484..ca10e8d47c 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTimed.java @@ -107,8 +107,6 @@ public void onNext(T t) { timer.replace(worker.schedule(this, timeout, unit)); } - - } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeInterval.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeInterval.java index 4e673da5e6..cf8b7087a7 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeInterval.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeInterval.java @@ -31,7 +31,6 @@ public FlowableTimeInterval(Flowable source, TimeUnit unit, Scheduler schedul this.unit = unit; } - @Override protected void subscribeActual(Subscriber> s) { source.subscribe(new TimeIntervalSubscriber(s, unit, scheduler)); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java index 6d8300f234..8363a5d0cb 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java @@ -208,6 +208,7 @@ static final class TimeoutFallbackSubscriber extends SubscriptionArbiter TimeoutFallbackSubscriber(Subscriber actual, Function> itemTimeoutIndicator, Publisher fallback) { + super(true); this.downstream = actual; this.itemTimeoutIndicator = itemTimeoutIndicator; this.task = new SequentialDisposable(); @@ -381,7 +382,7 @@ public void dispose() { @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(this.get()); + return this.get() == SubscriptionHelper.CANCELLED; } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java index 9be6bc847b..d25acdc3e3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java @@ -23,6 +23,8 @@ import io.reactivex.internal.subscriptions.*; import io.reactivex.plugins.RxJavaPlugins; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + public final class FlowableTimeoutTimed extends AbstractFlowableWithUpstream { final long timeout; final TimeUnit unit; @@ -134,7 +136,7 @@ public void onTimeout(long idx) { if (compareAndSet(idx, Long.MAX_VALUE)) { SubscriptionHelper.cancel(upstream); - downstream.onError(new TimeoutException()); + downstream.onError(new TimeoutException(timeoutMessage(timeout, unit))); worker.dispose(); } @@ -152,7 +154,6 @@ public void cancel() { } } - static final class TimeoutTask implements Runnable { final TimeoutSupport parent; @@ -195,6 +196,7 @@ static final class TimeoutFallbackSubscriber extends SubscriptionArbiter TimeoutFallbackSubscriber(Subscriber actual, long timeout, TimeUnit unit, Scheduler.Worker worker, Publisher fallback) { + super(true); this.downstream = actual; this.timeout = timeout; this.unit = unit; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableToList.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableToList.java index a49c3dcc8c..60508e3e7e 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableToList.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableToList.java @@ -44,12 +44,10 @@ protected void subscribeActual(Subscriber s) { source.subscribe(new ToListSubscriber(s, coll)); } - static final class ToListSubscriber> extends DeferredScalarSubscription implements FlowableSubscriber, Subscription { - private static final long serialVersionUID = -8134157938864266736L; Subscription upstream; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindow.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindow.java index bbe2f2b6e0..e9c1259b65 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindow.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindow.java @@ -55,7 +55,6 @@ static final class WindowExactSubscriber extends AtomicInteger implements FlowableSubscriber, Subscription, Runnable { - private static final long serialVersionUID = -2365647875069161133L; final Subscriber> downstream; @@ -164,7 +163,6 @@ static final class WindowSkipSubscriber extends AtomicInteger implements FlowableSubscriber, Subscription, Runnable { - private static final long serialVersionUID = -8792836352386833856L; final Subscriber> downstream; @@ -211,7 +209,6 @@ public void onNext(T t) { if (i == 0) { getAndIncrement(); - w = UnicastProcessor.create(bufferSize, this); window = w; @@ -292,7 +289,6 @@ static final class WindowOverlapSubscriber extends AtomicInteger implements FlowableSubscriber, Subscription, Runnable { - private static final long serialVersionUID = 2428527070996323976L; final Subscriber> downstream; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySelector.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySelector.java index 246b1be106..d9d6ffa517 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySelector.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySelector.java @@ -71,6 +71,8 @@ static final class WindowBoundaryMainSubscriber final AtomicLong windows = new AtomicLong(); + final AtomicBoolean stopWindows = new AtomicBoolean(); + WindowBoundaryMainSubscriber(Subscriber> actual, Publisher open, Function> close, int bufferSize) { super(actual, new MpscLinkedQueue()); @@ -89,14 +91,13 @@ public void onSubscribe(Subscription s) { downstream.onSubscribe(this); - if (cancelled) { + if (stopWindows.get()) { return; } OperatorWindowBoundaryOpenSubscriber os = new OperatorWindowBoundaryOpenSubscriber(this); if (boundary.compareAndSet(null, os)) { - windows.getAndIncrement(); s.request(Long.MAX_VALUE); open.subscribe(os); } @@ -177,7 +178,12 @@ public void request(long n) { @Override public void cancel() { - cancelled = true; + if (stopWindows.compareAndSet(false, true)) { + DisposableHelper.dispose(boundary); + if (windows.decrementAndGet() == 0) { + upstream.cancel(); + } + } } void dispose() { @@ -236,11 +242,10 @@ void drainLoop() { continue; } - if (cancelled) { + if (stopWindows.get()) { continue; } - w = UnicastProcessor.create(bufferSize); long r = requested(); @@ -251,7 +256,7 @@ void drainLoop() { produced(1); } } else { - cancelled = true; + cancel(); a.onError(new MissingBackpressureException("Could not deliver new window due to lack of requests")); continue; } @@ -261,7 +266,7 @@ void drainLoop() { try { p = ObjectHelper.requireNonNull(close.apply(wo.open), "The publisher supplied is null"); } catch (Throwable e) { - cancelled = true; + cancel(); a.onError(e); continue; } 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/flowable/FlowableWithLatestFrom.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFrom.java index 5f403fb150..00017d431d 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFrom.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFrom.java @@ -55,7 +55,7 @@ static final class WithLatestFromSubscriber extends AtomicReference final BiFunction combiner; - final AtomicReference s = new AtomicReference(); + final AtomicReference upstream = new AtomicReference(); final AtomicLong requested = new AtomicLong(); @@ -65,15 +65,16 @@ static final class WithLatestFromSubscriber extends AtomicReference this.downstream = actual; this.combiner = combiner; } + @Override public void onSubscribe(Subscription s) { - SubscriptionHelper.deferredSetOnce(this.s, requested, s); + SubscriptionHelper.deferredSetOnce(this.upstream, requested, s); } @Override public void onNext(T t) { if (!tryOnNext(t)) { - s.get().request(1); + upstream.get().request(1); } } @@ -111,12 +112,12 @@ public void onComplete() { @Override public void request(long n) { - SubscriptionHelper.deferredRequest(s, requested, n); + SubscriptionHelper.deferredRequest(upstream, requested, n); } @Override public void cancel() { - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); SubscriptionHelper.cancel(other); } @@ -125,7 +126,7 @@ public boolean setOther(Subscription o) { } public void otherError(Throwable e) { - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); downstream.onError(e); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java index f0a2231f02..6d6b949c33 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java @@ -131,9 +131,9 @@ static final class WithLatestFromSubscriber void subscribe(Publisher[] others, int n) { WithLatestInnerSubscriber[] subscribers = this.subscribers; - AtomicReference s = this.upstream; + AtomicReference upstream = this.upstream; for (int i = 0; i < n; i++) { - if (SubscriptionHelper.isCancelled(s.get())) { + if (upstream.get() == SubscriptionHelper.CANCELLED) { return; } others[i].subscribe(subscribers[i]); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableZip.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableZip.java index 1d585cc02c..b8516a93e9 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableZip.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableZip.java @@ -83,7 +83,6 @@ static final class ZipCoordinator extends AtomicInteger implements Subscription { - private static final long serialVersionUID = -2434867452883857743L; final Subscriber downstream; @@ -320,7 +319,6 @@ void drain() { } } - static final class ZipSubscriber extends AtomicReference implements FlowableSubscriber, Subscription { private static final long serialVersionUID = -4627193790118206028L; diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeAmb.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeAmb.java index fb6940c479..8efc69b24b 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeAmb.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeAmb.java @@ -64,65 +64,63 @@ protected void subscribeActual(MaybeObserver observer) { count = sources.length; } - AmbMaybeObserver parent = new AmbMaybeObserver(observer); - observer.onSubscribe(parent); + CompositeDisposable set = new CompositeDisposable(); + observer.onSubscribe(set); + + AtomicBoolean winner = new AtomicBoolean(); for (int i = 0; i < count; i++) { MaybeSource s = sources[i]; - if (parent.isDisposed()) { + if (set.isDisposed()) { return; } if (s == null) { - parent.onError(new NullPointerException("One of the MaybeSources is null")); + set.dispose(); + NullPointerException ex = new NullPointerException("One of the MaybeSources is null"); + if (winner.compareAndSet(false, true)) { + observer.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } return; } - s.subscribe(parent); + s.subscribe(new AmbMaybeObserver(observer, set, winner)); } if (count == 0) { observer.onComplete(); } - } static final class AmbMaybeObserver - extends AtomicBoolean - implements MaybeObserver, Disposable { - - - private static final long serialVersionUID = -7044685185359438206L; + implements MaybeObserver { final MaybeObserver downstream; - final CompositeDisposable set; + final AtomicBoolean winner; - AmbMaybeObserver(MaybeObserver downstream) { - this.downstream = downstream; - this.set = new CompositeDisposable(); - } + final CompositeDisposable set; - @Override - public void dispose() { - if (compareAndSet(false, true)) { - set.dispose(); - } - } + Disposable upstream; - @Override - public boolean isDisposed() { - return get(); + AmbMaybeObserver(MaybeObserver downstream, CompositeDisposable set, AtomicBoolean winner) { + this.downstream = downstream; + this.set = set; + this.winner = winner; } @Override public void onSubscribe(Disposable d) { + upstream = d; set.add(d); } @Override public void onSuccess(T value) { - if (compareAndSet(false, true)) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); downstream.onSuccess(value); @@ -131,7 +129,8 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - if (compareAndSet(false, true)) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); downstream.onError(e); @@ -142,12 +141,12 @@ public void onError(Throwable e) { @Override public void onComplete() { - if (compareAndSet(false, true)) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); downstream.onComplete(); } } - } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserver.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserver.java index 1a3da7d04b..9dc56e137f 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserver.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserver.java @@ -33,7 +33,6 @@ public final class MaybeCallbackObserver extends AtomicReference implements MaybeObserver, Disposable, LambdaConsumerIntrospection { - private static final long serialVersionUID = -6076952298809384986L; final Consumer onSuccess; diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCreate.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCreate.java index 5a3852662a..e328a2b3c7 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCreate.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCreate.java @@ -59,7 +59,6 @@ static final class Emitter this.downstream = downstream; } - private static final long serialVersionUID = -2467358622224974244L; @Override diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java index 55049d5c95..d3a0f783e2 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java @@ -65,7 +65,7 @@ public void dispose() { @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(other.get()); + return other.get() == SubscriptionHelper.CANCELLED; } @Override diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminate.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminate.java new file mode 100644 index 0000000000..81d0d8af34 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminate.java @@ -0,0 +1,90 @@ +/** + * 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.maybe; + +import io.reactivex.Maybe; +import io.reactivex.MaybeObserver; +import io.reactivex.MaybeSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; + +public final class MaybeDoOnTerminate extends Maybe { + + final MaybeSource source; + + final Action onTerminate; + + public MaybeDoOnTerminate(MaybeSource source, Action onTerminate) { + this.source = source; + this.onTerminate = onTerminate; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new DoOnTerminate(observer)); + } + + final class DoOnTerminate implements MaybeObserver { + final MaybeObserver downstream; + + DoOnTerminate(MaybeObserver observer) { + this.downstream = observer; + } + + @Override + public void onSubscribe(Disposable d) { + downstream.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + + downstream.onError(e); + } + + @Override + public void onComplete() { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeEqualSingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeEqualSingle.java index 4f843635aa..f5f83fbcfc 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeEqualSingle.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeEqualSingle.java @@ -127,7 +127,6 @@ static final class EqualObserver extends AtomicReference implements MaybeObserver { - private static final long serialVersionUID = -3031974433025990931L; final EqualCoordinator parent; diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilter.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilter.java index fd556c3a66..763ad258e0 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilter.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilter.java @@ -101,8 +101,5 @@ public void onError(Throwable e) { public void onComplete() { downstream.onComplete(); } - - } - } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowable.java index 5ea9adb386..f4d174d924 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowable.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowable.java @@ -158,7 +158,6 @@ void fastPath(Subscriber a, Iterator iterator) { return; } - boolean b; try { diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservable.java index 4e5755fb8f..5513eb5e8f 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservable.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservable.java @@ -128,7 +128,6 @@ public void onSuccess(T value) { return; } - boolean b; try { diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapNotification.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapNotification.java index f5bb71e110..81eb167484 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapNotification.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapNotification.java @@ -56,7 +56,6 @@ static final class FlatMapMaybeObserver extends AtomicReference implements MaybeObserver, Disposable { - private static final long serialVersionUID = 4375739915521278546L; final MaybeObserver downstream; diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatten.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatten.java index b05aebd236..6463ef270b 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatten.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatten.java @@ -46,7 +46,6 @@ static final class FlatMapMaybeObserver extends AtomicReference implements MaybeObserver, Disposable { - private static final long serialVersionUID = 4375739915521278546L; final MaybeObserver downstream; diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMap.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMap.java index cda167855c..7d7c7a47b5 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMap.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMap.java @@ -98,8 +98,5 @@ public void onError(Throwable e) { public void onComplete() { downstream.onComplete(); } - - } - } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMaterialize.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMaterialize.java new file mode 100644 index 0000000000..2b74829ba1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMaterialize.java @@ -0,0 +1,40 @@ +/** + * 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.maybe; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.internal.operators.mixed.MaterializeSingleObserver; + +/** + * Turn the signal types of a Maybe source into a single Notification of + * equal kind. + * + * @param the element type of the source + * @since 2.2.4 - experimental + */ +@Experimental +public final class MaybeMaterialize extends Single> { + + final Maybe source; + + public MaybeMaterialize(Maybe source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver> observer) { + source.subscribe(new MaterializeSingleObserver(observer)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMergeArray.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMergeArray.java index 762452ba7a..1edfdd69da 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMergeArray.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMergeArray.java @@ -318,7 +318,6 @@ static final class MpscFillOnceSimpleQueue extends AtomicReferenceArray implements SimpleQueueWithConsumerIndex { - private static final long serialVersionUID = -7969063454040569579L; final AtomicInteger producerIndex; diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeObserveOn.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeObserveOn.java index a3d83612d5..cce201fedb 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeObserveOn.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeObserveOn.java @@ -42,7 +42,6 @@ static final class ObserveOnMaybeObserver extends AtomicReference implements MaybeObserver, Disposable, Runnable { - private static final long serialVersionUID = 8571289934935992137L; final MaybeObserver downstream; diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorNext.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorNext.java index 5591d41c20..f7fff884ad 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorNext.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorNext.java @@ -50,7 +50,6 @@ static final class OnErrorNextMaybeObserver extends AtomicReference implements MaybeObserver, Disposable { - private static final long serialVersionUID = 2026620218879969836L; final MaybeObserver downstream; diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmpty.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmpty.java index 77d2d8f333..68d3db37d7 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmpty.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmpty.java @@ -99,18 +99,22 @@ static final class OtherMaybeObserver implements MaybeObserver { this.downstream = actual; this.parent = parent; } + @Override public void onSubscribe(Disposable d) { DisposableHelper.setOnce(parent, d); } + @Override public void onSuccess(T value) { downstream.onSuccess(value); } + @Override public void onError(Throwable e) { downstream.onError(e); } + @Override public void onComplete() { downstream.onComplete(); diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingle.java index 798b3ef24f..bd94beb3ea 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingle.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingle.java @@ -106,14 +106,17 @@ static final class OtherSingleObserver implements SingleObserver { this.downstream = actual; this.parent = parent; } + @Override public void onSubscribe(Disposable d) { DisposableHelper.setOnce(parent, d); } + @Override public void onSuccess(T value) { downstream.onSuccess(value); } + @Override public void onError(Throwable e) { downstream.onError(e); diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutMaybe.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutMaybe.java index d162573139..83d22cf232 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutMaybe.java @@ -54,7 +54,6 @@ static final class TimeoutMainMaybeObserver extends AtomicReference implements MaybeObserver, Disposable { - private static final long serialVersionUID = -5955289211445418871L; final MaybeObserver downstream; @@ -141,7 +140,6 @@ static final class TimeoutOtherMaybeObserver extends AtomicReference implements MaybeObserver { - private static final long serialVersionUID = 8663801314800248617L; final TimeoutMainMaybeObserver parent; @@ -174,7 +172,6 @@ static final class TimeoutFallbackMaybeObserver extends AtomicReference implements MaybeObserver { - private static final long serialVersionUID = 8663801314800248617L; final MaybeObserver downstream; diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java index 64ab706f5e..f0d690d567 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java @@ -57,7 +57,6 @@ static final class TimeoutMainMaybeObserver extends AtomicReference implements MaybeObserver, Disposable { - private static final long serialVersionUID = -5955289211445418871L; final MaybeObserver downstream; @@ -144,7 +143,6 @@ static final class TimeoutOtherMaybeObserver extends AtomicReference implements FlowableSubscriber { - private static final long serialVersionUID = 8663801314800248617L; final TimeoutMainMaybeObserver parent; @@ -179,7 +177,6 @@ static final class TimeoutFallbackMaybeObserver extends AtomicReference implements MaybeObserver { - private static final long serialVersionUID = 8663801314800248617L; final MaybeObserver downstream; @@ -208,6 +205,4 @@ public void onComplete() { downstream.onComplete(); } } - - } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeUsing.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeUsing.java index 786772eacf..4628d1261c 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeUsing.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeUsing.java @@ -99,7 +99,6 @@ static final class UsingObserver extends AtomicReference implements MaybeObserver, Disposable { - private static final long serialVersionUID = -674404550052917487L; final MaybeObserver downstream; diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipArray.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipArray.java index 430d3ff2b6..d9cc0028af 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipArray.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipArray.java @@ -39,7 +39,6 @@ protected void subscribeActual(MaybeObserver observer) { MaybeSource[] sources = this.sources; int n = sources.length; - if (n == 1) { sources[0].subscribe(new MaybeMap.MapMaybeObserver(observer, new SingletonArrayFunc())); return; @@ -66,7 +65,6 @@ protected void subscribeActual(MaybeObserver observer) { static final class ZipCoordinator extends AtomicInteger implements Disposable { - private static final long serialVersionUID = -5556924161382950569L; final MaybeObserver downstream; diff --git a/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservable.java b/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservable.java index 39f1a79a19..454c45b4c1 100644 --- a/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservable.java +++ b/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservable.java @@ -81,7 +81,6 @@ public void onComplete() { } } - @Override public void dispose() { DisposableHelper.dispose(this); diff --git a/src/main/java/io/reactivex/internal/operators/mixed/MaterializeSingleObserver.java b/src/main/java/io/reactivex/internal/operators/mixed/MaterializeSingleObserver.java new file mode 100644 index 0000000000..ef8a87076d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/MaterializeSingleObserver.java @@ -0,0 +1,71 @@ +/** + * 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.mixed; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * A consumer that implements the consumer types of Maybe, Single and Completable + * and turns their signals into Notifications for a SingleObserver. + * @param the element type of the source + * @since 2.2.4 - experimental + */ +@Experimental +public final class MaterializeSingleObserver +implements SingleObserver, MaybeObserver, CompletableObserver, Disposable { + + final SingleObserver> downstream; + + Disposable upstream; + + public MaterializeSingleObserver(SingleObserver> downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onComplete() { + downstream.onSuccess(Notification.createOnComplete()); + } + + @Override + public void onSuccess(T t) { + downstream.onSuccess(Notification.createOnNext(t)); + } + + @Override + public void onError(Throwable e) { + downstream.onSuccess(Notification.createOnError(e)); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void dispose() { + upstream.dispose(); + } +} 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 776721c84e..24a7cb7701 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java @@ -44,7 +44,6 @@ static final class BlockingObservableIterator extends AtomicReference implements io.reactivex.Observer, Iterator, Disposable { - private static final long serialVersionUID = 6695226475494099826L; final SpscLinkedArrayQueue queue; @@ -54,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); @@ -65,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) { @@ -81,7 +87,7 @@ public boolean hasNext() { BlockingHelper.verifyNonBlocking(); lock.lock(); try { - while (!done && queue.isEmpty()) { + while (!done && queue.isEmpty() && !isDisposed()) { condition.await(); } } finally { @@ -147,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 44156e1171..04940be5e6 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java +++ b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; - import java.util.*; import io.reactivex.ObservableSource; @@ -43,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/ObservableAll.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAll.java index 71d0c32ebf..2349ce4893 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableAll.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAll.java @@ -43,6 +43,7 @@ static final class AllObserver implements Observer, Disposable { this.downstream = actual; this.predicate = predicate; } + @Override public void onSubscribe(Disposable d) { if (DisposableHelper.validate(this.upstream, d)) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAllSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAllSingle.java index 3089007d6d..1fb756233d 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableAllSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAllSingle.java @@ -51,6 +51,7 @@ static final class AllObserver implements Observer, Disposable { this.downstream = actual; this.predicate = predicate; } + @Override public void onSubscribe(Disposable d) { if (DisposableHelper.validate(this.upstream, d)) { 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/ObservableAny.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAny.java index 92f3aa02cf..c1500c3fb6 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableAny.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAny.java @@ -44,6 +44,7 @@ static final class AnyObserver implements Observer, Disposable { this.downstream = actual; this.predicate = predicate; } + @Override public void onSubscribe(Disposable d) { if (DisposableHelper.validate(this.upstream, d)) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAnySingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAnySingle.java index 87f0d7c64c..b8c7001ed5 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableAnySingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAnySingle.java @@ -53,6 +53,7 @@ static final class AnyObserver implements Observer, Disposable { this.downstream = actual; this.predicate = predicate; } + @Override public void onSubscribe(Disposable d) { if (DisposableHelper.validate(this.upstream, d)) { 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> final Worker w; final List buffers; - Disposable upstream; BufferSkipBoundedObserver(Observer actual, @@ -505,10 +504,12 @@ public void onComplete() { buffer = null; } - queue.offer(b); - done = true; - if (enter()) { - QueueDrainHelper.drainLoop(queue, downstream, false, this, this); + if (b != null) { + queue.offer(b); + done = true; + if (enter()) { + QueueDrainHelper.drainLoop(queue, downstream, false, this, this); + } } } @@ -517,7 +518,6 @@ public void accept(Observer a, U v) { a.onNext(v); } - @Override public void dispose() { if (!cancelled) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCache.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCache.java index 4d7563608f..fdc3477b75 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCache.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCache.java @@ -17,10 +17,6 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; -import io.reactivex.internal.disposables.SequentialDisposable; -import io.reactivex.internal.functions.ObjectHelper; -import io.reactivex.internal.util.*; -import io.reactivex.plugins.RxJavaPlugins; /** * An observable which auto-connects to another observable, caches the elements @@ -28,61 +24,94 @@ * * @param the source element type */ -public final class ObservableCache extends AbstractObservableWithUpstream { - /** The cache and replay state. */ - final CacheState state; +public final class ObservableCache extends AbstractObservableWithUpstream +implements Observer { + /** + * The subscription to the source should happen at most once. + */ final AtomicBoolean once; /** - * Creates a cached Observable with a default capacity hint of 16. - * @param the value type - * @param source the source Observable to cache - * @return the CachedObservable instance + * The number of items per cached nodes. */ - public static Observable from(Observable source) { - return from(source, 16); - } + final int capacityHint; /** - * Creates a cached Observable with the given capacity hint. - * @param the value type - * @param source the source Observable to cache - * @param capacityHint the hint for the internal buffer size - * @return the CachedObservable instance + * The current known array of observer state to notify. */ - public static Observable from(Observable source, int capacityHint) { - ObjectHelper.verifyPositive(capacityHint, "capacityHint"); - CacheState state = new CacheState(source, capacityHint); - return RxJavaPlugins.onAssembly(new ObservableCache(source, state)); - } + final AtomicReference[]> observers; /** - * Private constructor because state needs to be shared between the Observable body and - * the onSubscribe function. - * @param source the source Observable to cache - * @param state the cache state object + * A shared instance of an empty array of observers to avoid creating + * a new empty array when all observers dispose. + */ + @SuppressWarnings("rawtypes") + static final CacheDisposable[] EMPTY = new CacheDisposable[0]; + /** + * A shared instance indicating the source has no more events and there + * is no need to remember observers anymore. */ - private ObservableCache(Observable source, CacheState state) { + @SuppressWarnings("rawtypes") + static final CacheDisposable[] TERMINATED = new CacheDisposable[0]; + + /** + * The total number of elements in the list available for reads. + */ + volatile long size; + + /** + * The starting point of the cached items. + */ + final Node head; + + /** + * The current tail of the linked structure holding the items. + */ + Node tail; + + /** + * How many items have been put into the tail node so far. + */ + int tailOffset; + + /** + * If {@link #observers} is {@link #TERMINATED}, this holds the terminal error if not null. + */ + Throwable error; + + /** + * True if the source has terminated. + */ + volatile boolean done; + + /** + * Constructs an empty, non-connected cache. + * @param source the source to subscribe to for the first incoming observer + * @param capacityHint the number of items expected (reduce allocation frequency) + */ + @SuppressWarnings("unchecked") + public ObservableCache(Observable source, int capacityHint) { super(source); - this.state = state; + this.capacityHint = capacityHint; this.once = new AtomicBoolean(); + Node n = new Node(capacityHint); + this.head = n; + this.tail = n; + this.observers = new AtomicReference[]>(EMPTY); } @Override protected void subscribeActual(Observer t) { - // we can connect first because we replay everything anyway - ReplayDisposable rp = new ReplayDisposable(t, state); - t.onSubscribe(rp); - - state.addChild(rp); + CacheDisposable consumer = new CacheDisposable(t, this); + t.onSubscribe(consumer); + add(consumer); - // we ensure a single connection here to save an instance field of AtomicBoolean in state. if (!once.get() && once.compareAndSet(false, true)) { - state.connect(); + source.subscribe(this); + } else { + replay(consumer); } - - rp.replay(); } /** @@ -90,286 +119,281 @@ protected void subscribeActual(Observer t) { * @return true if already connected */ /* public */boolean isConnected() { - return state.isConnected; + return once.get(); } /** * Returns true if there are observers subscribed to this observable. - * @return true if the cache has downstream Observers + * @return true if the cache has observers */ /* public */ boolean hasObservers() { - return state.observers.get().length != 0; + return observers.get().length != 0; } /** * Returns the number of events currently cached. - * @return the current number of elements in the cache + * @return the number of currently cached event count */ - /* public */ int cachedEventCount() { - return state.size(); + /* public */ long cachedEventCount() { + return size; } /** - * Contains the active child observers and the values to replay. - * - * @param + * Atomically adds the consumer to the {@link #observers} copy-on-write array + * if the source has not yet terminated. + * @param consumer the consumer to add */ - static final class CacheState extends LinkedArrayList implements Observer { - /** The source observable to connect to. */ - final Observable source; - /** Holds onto the subscriber connected to source. */ - final SequentialDisposable connection; - /** Guarded by connection (not this). */ - final AtomicReference[]> observers; - /** The default empty array of observers. */ - @SuppressWarnings("rawtypes") - static final ReplayDisposable[] EMPTY = new ReplayDisposable[0]; - /** The default empty array of observers. */ - @SuppressWarnings("rawtypes") - static final ReplayDisposable[] TERMINATED = new ReplayDisposable[0]; - - /** Set to true after connection. */ - volatile boolean isConnected; - /** - * Indicates that the source has completed emitting values or the - * Observable was forcefully terminated. - */ - boolean sourceDone; + void add(CacheDisposable consumer) { + for (;;) { + CacheDisposable[] current = observers.get(); + if (current == TERMINATED) { + return; + } + int n = current.length; - @SuppressWarnings("unchecked") - CacheState(Observable source, int capacityHint) { - super(capacityHint); - this.source = source; - this.observers = new AtomicReference[]>(EMPTY); - this.connection = new SequentialDisposable(); + @SuppressWarnings("unchecked") + CacheDisposable[] next = new CacheDisposable[n + 1]; + System.arraycopy(current, 0, next, 0, n); + next[n] = consumer; + + if (observers.compareAndSet(current, next)) { + return; + } } - /** - * Adds a ReplayDisposable to the observers array atomically. - * @param p the target ReplayDisposable wrapping a downstream Observer with additional state - * @return true if the disposable was added, false otherwise - */ - public boolean addChild(ReplayDisposable p) { - // guarding by connection to save on allocating another object - // thus there are two distinct locks guarding the value-addition and child come-and-go - for (;;) { - ReplayDisposable[] a = observers.get(); - if (a == TERMINATED) { - return false; - } - int n = a.length; - - @SuppressWarnings("unchecked") - ReplayDisposable[] b = new ReplayDisposable[n + 1]; - System.arraycopy(a, 0, b, 0, n); - b[n] = p; - if (observers.compareAndSet(a, b)) { - return true; + } + + /** + * Atomically removes the consumer from the {@link #observers} copy-on-write array. + * @param consumer the consumer to remove + */ + @SuppressWarnings("unchecked") + void remove(CacheDisposable consumer) { + for (;;) { + CacheDisposable[] current = observers.get(); + int n = current.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (current[i] == consumer) { + j = i; + break; } } + + if (j < 0) { + return; + } + CacheDisposable[] next; + + if (n == 1) { + next = EMPTY; + } else { + next = new CacheDisposable[n - 1]; + System.arraycopy(current, 0, next, 0, j); + System.arraycopy(current, j + 1, next, j, n - j - 1); + } + + if (observers.compareAndSet(current, next)) { + return; + } } - /** - * Removes the ReplayDisposable (if present) from the observers array atomically. - * @param p the target ReplayDisposable wrapping a downstream Observer with additional state - */ - @SuppressWarnings("unchecked") - public void removeChild(ReplayDisposable p) { - for (;;) { - ReplayDisposable[] a = observers.get(); - int n = a.length; - if (n == 0) { - return; - } - int j = -1; - for (int i = 0; i < n; i++) { - if (a[i].equals(p)) { - j = i; - break; - } - } - if (j < 0) { - return; - } - ReplayDisposable[] b; - if (n == 1) { - b = EMPTY; + } + + /** + * Replays the contents of this cache to the given consumer based on its + * current state and number of items requested by it. + * @param consumer the consumer to continue replaying items to + */ + void replay(CacheDisposable consumer) { + // make sure there is only one replay going on at a time + if (consumer.getAndIncrement() != 0) { + return; + } + + // see if there were more replay request in the meantime + int missed = 1; + // read out state into locals upfront to avoid being re-read due to volatile reads + long index = consumer.index; + int offset = consumer.offset; + Node node = consumer.node; + Observer downstream = consumer.downstream; + int capacity = capacityHint; + + for (;;) { + // if the consumer got disposed, clear the node and quit + if (consumer.disposed) { + consumer.node = null; + return; + } + + // first see if the source has terminated, read order matters! + boolean sourceDone = done; + // and if the number of items is the same as this consumer has received + boolean empty = size == index; + + // if the source is done and we have all items so far, terminate the consumer + if (sourceDone && empty) { + // release the node object to avoid leaks through retained consumers + consumer.node = null; + // if error is not null then the source failed + Throwable ex = error; + if (ex != null) { + downstream.onError(ex); } else { - b = new ReplayDisposable[n - 1]; - System.arraycopy(a, 0, b, 0, j); - System.arraycopy(a, j + 1, b, j, n - j - 1); + downstream.onComplete(); } - if (observers.compareAndSet(a, b)) { - return; + return; + } + + // there are still items not sent to the consumer + if (!empty) { + // if the offset in the current node has reached the node capacity + if (offset == capacity) { + // switch to the subsequent node + node = node.next; + // reset the in-node offset + offset = 0; } + + // emit the cached item + downstream.onNext(node.values[offset]); + + // move the node offset forward + offset++; + // move the total consumed item count forward + index++; + + // retry for the next item/terminal event if any + continue; } - } - @Override - public void onSubscribe(Disposable d) { - connection.update(d); + // commit the changed references back + consumer.index = index; + consumer.offset = offset; + consumer.node = node; + // release the changes and see if there were more replay request in the meantime + missed = consumer.addAndGet(-missed); + if (missed == 0) { + break; + } } + } - /** - * Connects the cache to the source. - * Make sure this is called only once. - */ - public void connect() { - source.subscribe(this); - isConnected = true; + @Override + public void onSubscribe(Disposable d) { + // we can't do much with the upstream disposable + } + + @Override + public void onNext(T t) { + int tailOffset = this.tailOffset; + // if the current tail node is full, create a fresh node + if (tailOffset == capacityHint) { + Node n = new Node(tailOffset); + n.values[0] = t; + this.tailOffset = 1; + tail.next = n; + tail = n; + } else { + tail.values[tailOffset] = t; + this.tailOffset = tailOffset + 1; } - @Override - public void onNext(T t) { - if (!sourceDone) { - Object o = NotificationLite.next(t); - add(o); - for (ReplayDisposable rp : observers.get()) { - rp.replay(); - } - } + size++; + for (CacheDisposable consumer : observers.get()) { + replay(consumer); } - @SuppressWarnings("unchecked") - @Override - public void onError(Throwable e) { - if (!sourceDone) { - sourceDone = true; - Object o = NotificationLite.error(e); - add(o); - connection.dispose(); - for (ReplayDisposable rp : observers.getAndSet(TERMINATED)) { - rp.replay(); - } - } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable t) { + error = t; + done = true; + for (CacheDisposable consumer : observers.getAndSet(TERMINATED)) { + replay(consumer); } - @SuppressWarnings("unchecked") - @Override - public void onComplete() { - if (!sourceDone) { - sourceDone = true; - Object o = NotificationLite.complete(); - add(o); - connection.dispose(); - for (ReplayDisposable rp : observers.getAndSet(TERMINATED)) { - rp.replay(); - } - } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + done = true; + for (CacheDisposable consumer : observers.getAndSet(TERMINATED)) { + replay(consumer); } } /** - * Keeps track of the current request amount and the replay position for a child Observer. - * - * @param + * Hosts the downstream consumer and its current requested and replay states. + * {@code this} holds the work-in-progress counter for the serialized replay. + * @param the value type */ - static final class ReplayDisposable - extends AtomicInteger + static final class CacheDisposable extends AtomicInteger implements Disposable { - private static final long serialVersionUID = 7058506693698832024L; - /** The actual child subscriber. */ - final Observer child; - /** The cache state object. */ - final CacheState state; + private static final long serialVersionUID = 6770240836423125754L; - /** - * Contains the reference to the buffer segment in replay. - * Accessed after reading state.size() and when emitting == true. - */ - Object[] currentBuffer; - /** - * Contains the index into the currentBuffer where the next value is expected. - * Accessed after reading state.size() and when emitting == true. - */ - int currentIndexInBuffer; - /** - * Contains the absolute index up until the values have been replayed so far. - */ - int index; + final Observer downstream; - /** Set if the ReplayDisposable has been cancelled/disposed. */ - volatile boolean cancelled; + final ObservableCache parent; - ReplayDisposable(Observer child, CacheState state) { - this.child = child; - this.state = state; - } + Node node; - @Override - public boolean isDisposed() { - return cancelled; + int offset; + + long index; + + volatile boolean disposed; + + /** + * Constructs a new instance with the actual downstream consumer and + * the parent cache object. + * @param downstream the actual consumer + * @param parent the parent that holds onto the cached items + */ + CacheDisposable(Observer downstream, ObservableCache parent) { + this.downstream = downstream; + this.parent = parent; + this.node = parent.head; } + @Override public void dispose() { - if (!cancelled) { - cancelled = true; - state.removeChild(this); + if (!disposed) { + disposed = true; + parent.remove(this); } } - /** - * Continue replaying available values if there are requests for them. - */ - public void replay() { - // make sure there is only a single thread emitting - if (getAndIncrement() != 0) { - return; - } - - final Observer child = this.child; - int missed = 1; - - for (;;) { + @Override + public boolean isDisposed() { + return disposed; + } + } - if (cancelled) { - return; - } + /** + * Represents a segment of the cached item list as + * part of a linked-node-list structure. + * @param the element type + */ + static final class Node { - // read the size, if it is non-zero, we can safely read the head and - // read values up to the given absolute index - int s = state.size(); - if (s != 0) { - Object[] b = currentBuffer; - - // latch onto the very first buffer now that it is available. - if (b == null) { - b = state.head(); - currentBuffer = b; - } - final int n = b.length - 1; - int j = index; - int k = currentIndexInBuffer; - - while (j < s) { - if (cancelled) { - return; - } - if (k == n) { - b = (Object[])b[n]; - k = 0; - } - Object o = b[k]; - - if (NotificationLite.accept(o, child)) { - return; - } - - k++; - j++; - } - - if (cancelled) { - return; - } - - index = j; - currentIndexInBuffer = k; - currentBuffer = b; + /** + * The array of values held by this node. + */ + final T[] values; - } + /** + * The next node if not null. + */ + volatile Node next; - missed = addAndGet(-missed); - if (missed == 0) { - break; - } - } + @SuppressWarnings("unchecked") + Node(int capacityHint) { + this.values = (T[])new Object[capacityHint]; } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCollect.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCollect.java index 8deff52c47..761fabd49e 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCollect.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCollect.java @@ -69,7 +69,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -80,7 +79,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCollectSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCollectSingle.java index 04753eae0a..59c34a0048 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCollectSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCollectSingle.java @@ -77,7 +77,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -88,7 +87,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCombineLatest.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCombineLatest.java index 27b869439a..56b62cdfeb 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCombineLatest.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCombineLatest.java @@ -43,14 +43,13 @@ public ObservableCombineLatest(ObservableSource[] sources, this.delayError = delayError; } - @Override @SuppressWarnings("unchecked") 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)]; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMap.java index 742661690a..a59841d501 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMap.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMap.java @@ -40,6 +40,7 @@ public ObservableConcatMap(ObservableSource source, Function observer) { @@ -82,6 +83,7 @@ static final class SourceObserver extends AtomicInteger implements Observe this.bufferSize = bufferSize; this.inner = new InnerObserver(actual, this); } + @Override public void onSubscribe(Disposable d) { if (DisposableHelper.validate(this.upstream, d)) { @@ -117,6 +119,7 @@ public void onSubscribe(Disposable d) { downstream.onSubscribe(this); } } + @Override public void onNext(T t) { if (done) { @@ -127,6 +130,7 @@ public void onNext(T t) { } drain(); } + @Override public void onError(Throwable t) { if (done) { @@ -137,6 +141,7 @@ public void onError(Throwable t) { dispose(); downstream.onError(t); } + @Override public void onComplete() { if (done) { @@ -239,18 +244,20 @@ static final class InnerObserver extends AtomicReference implemen @Override public void onSubscribe(Disposable d) { - DisposableHelper.set(this, d); + DisposableHelper.replace(this, d); } @Override public void onNext(U t) { downstream.onNext(t); } + @Override public void onError(Throwable t) { parent.dispose(); downstream.onError(t); } + @Override public void onComplete() { parent.innerComplete(); @@ -266,7 +273,6 @@ static final class ConcatMapDelayErrorObserver extends AtomicInteger implements Observer, Disposable { - private static final long serialVersionUID = -6951100001833242599L; final Observer downstream; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMapEager.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMapEager.java index cb15b10f65..7028fdcf62 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMapEager.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMapEager.java @@ -162,10 +162,21 @@ public void onComplete() { @Override public void dispose() { + if (cancelled) { + return; + } cancelled = true; + upstream.dispose(); + + drainAndDispose(); + } + + void drainAndDispose() { if (getAndIncrement() == 0) { - queue.clear(); - disposeAll(); + do { + queue.clear(); + disposeAll(); + } while (decrementAndGet() != 0); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCount.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCount.java index bf961d47c0..bae08e040b 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCount.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCount.java @@ -46,7 +46,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCountSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCountSingle.java index 1568e00f97..6e534b0dcc 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCountSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCountSingle.java @@ -54,7 +54,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCreate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCreate.java index 1e48bdabee..03ba3f1186 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCreate.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCreate.java @@ -48,7 +48,6 @@ static final class CreateEmitter extends AtomicReference implements ObservableEmitter, Disposable { - private static final long serialVersionUID = -3434801548987643227L; final Observer observer; 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/ObservableDebounceTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounceTimed.java index d37b99ecfd..49f490924a 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounceTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounceTimed.java @@ -117,6 +117,7 @@ public void onComplete() { if (d != null) { d.dispose(); } + @SuppressWarnings("unchecked") DebounceEmitter de = (DebounceEmitter)d; if (de != null) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDefer.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDefer.java index 411530c81f..adc2c29008 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDefer.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDefer.java @@ -25,6 +25,7 @@ public final class ObservableDefer extends Observable { public ObservableDefer(Callable> supplier) { this.supplier = supplier; } + @Override public void subscribeActual(Observer observer) { ObservableSource pub; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDematerialize.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDematerialize.java index 70dd6502b5..2f864152ee 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDematerialize.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDematerialize.java @@ -15,29 +15,38 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.plugins.RxJavaPlugins; -public final class ObservableDematerialize extends AbstractObservableWithUpstream, T> { +public final class ObservableDematerialize extends AbstractObservableWithUpstream { - public ObservableDematerialize(ObservableSource> source) { + final Function> selector; + + public ObservableDematerialize(ObservableSource source, Function> selector) { super(source); + this.selector = selector; } @Override - public void subscribeActual(Observer t) { - source.subscribe(new DematerializeObserver(t)); + public void subscribeActual(Observer observer) { + source.subscribe(new DematerializeObserver(observer, selector)); } - static final class DematerializeObserver implements Observer>, Disposable { - final Observer downstream; + static final class DematerializeObserver implements Observer, Disposable { + final Observer downstream; + + final Function> selector; boolean done; Disposable upstream; - DematerializeObserver(Observer downstream) { + DematerializeObserver(Observer downstream, Function> selector) { this.downstream = downstream; + this.selector = selector; } @Override @@ -49,7 +58,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -60,24 +68,37 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override - public void onNext(Notification t) { + public void onNext(T item) { if (done) { - if (t.isOnError()) { - RxJavaPlugins.onError(t.getError()); + if (item instanceof Notification) { + Notification notification = (Notification)item; + if (notification.isOnError()) { + RxJavaPlugins.onError(notification.getError()); + } } return; } - if (t.isOnError()) { + + Notification notification; + + try { + notification = ObjectHelper.requireNonNull(selector.apply(item), "The selector returned a null Notification"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + return; + } + if (notification.isOnError()) { upstream.dispose(); - onError(t.getError()); + onError(notification.getError()); } - else if (t.isOnComplete()) { + else if (notification.isOnComplete()) { upstream.dispose(); onComplete(); } else { - downstream.onNext(t.getValue()); + downstream.onNext(notification.getValue()); } } @@ -91,6 +112,7 @@ public void onError(Throwable t) { downstream.onError(t); } + @Override public void onComplete() { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnEach.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnEach.java index dc3b4561b2..a88218cbd5 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnEach.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnEach.java @@ -74,7 +74,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -85,7 +84,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAt.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAt.java index 7abc421572..5897fddfed 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAt.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAt.java @@ -31,6 +31,7 @@ public ObservableElementAt(ObservableSource source, long index, T defaultValu this.defaultValue = defaultValue; this.errorOnFewer = errorOnFewer; } + @Override public void subscribeActual(Observer t) { source.subscribe(new ElementAtObserver(t, index, defaultValue, errorOnFewer)); @@ -63,7 +64,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -74,7 +74,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtMaybe.java index 84f8d5baf7..921edd6ab3 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtMaybe.java @@ -26,6 +26,7 @@ public ObservableElementAtMaybe(ObservableSource source, long index) { this.source = source; this.index = index; } + @Override public void subscribeActual(MaybeObserver t) { source.subscribe(new ElementAtObserver(t, index)); @@ -59,7 +60,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -70,7 +70,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtSingle.java index 6c73a4e91a..7de1fa7bef 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtSingle.java @@ -67,7 +67,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -78,7 +77,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableError.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableError.java index bc375ddf38..b0eeb95c56 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableError.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableError.java @@ -25,6 +25,7 @@ public final class ObservableError extends Observable { public ObservableError(Callable errorSupplier) { this.errorSupplier = errorSupplier; } + @Override public void subscribeActual(Observer observer) { Throwable error; 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 76247e8e08..73a5306513 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java @@ -232,7 +232,6 @@ boolean tryEmitScalar(Callable value) { return true; } - if (get() == 0 && compareAndSet(0, 1)) { downstream.onNext(u); if (decrementAndGet() == 0) { @@ -335,30 +334,34 @@ void drainLoop() { if (checkTerminate()) { return; } + int innerCompleted = 0; SimplePlainQueue svq = queue; if (svq != null) { for (;;) { - U o; - for (;;) { - if (checkTerminate()) { - return; - } - - o = svq.poll(); + if (checkTerminate()) { + return; + } - if (o == null) { - break; - } + U o = svq.poll(); - child.onNext(o); - } if (o == null) { break; } + + 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(); @@ -383,7 +386,6 @@ void drainLoop() { return; } - boolean innerCompleted = false; if (n != 0) { long startId = lastId; int index = lastIndex; @@ -413,19 +415,13 @@ void drainLoop() { if (checkTerminate()) { return; } + @SuppressWarnings("unchecked") InnerObserver is = (InnerObserver)inner[j]; - - for (;;) { - if (checkTerminate()) { - return; - } - SimpleQueue q = is.queue; - if (q == null) { - break; - } - U o; + SimpleQueue q = is.queue; + if (q != null) { for (;;) { + U o; try { o = q.poll(); } catch (Throwable ex) { @@ -436,8 +432,11 @@ void drainLoop() { return; } removeInner(is); - innerCompleted = true; - i++; + innerCompleted++; + j++; + if (j == n) { + j = 0; + } continue sourceLoop; } if (o == null) { @@ -450,10 +449,8 @@ void drainLoop() { return; } } - if (o == null) { - break; - } } + boolean innerDone = is.done; SimpleQueue innerQueue = is.queue; if (innerDone && (innerQueue == null || innerQueue.isEmpty())) { @@ -461,7 +458,7 @@ void drainLoop() { if (checkTerminate()) { return; } - innerCompleted = true; + innerCompleted++; } j++; @@ -473,20 +470,14 @@ void drainLoop() { lastId = inner[j].id; } - if (innerCompleted) { + if (innerCompleted != 0) { if (maxConcurrency != Integer.MAX_VALUE) { - 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; @@ -494,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; @@ -542,6 +547,7 @@ static final class InnerObserver extends AtomicReference this.id = id; this.parent = parent; } + @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { @@ -564,6 +570,7 @@ public void onSubscribe(Disposable d) { } } } + @Override public void onNext(U t) { if (fusionMode == QueueDisposable.NONE) { @@ -572,6 +579,7 @@ public void onNext(U t) { parent.drain(); } } + @Override public void onError(Throwable t) { if (parent.errors.addThrowable(t)) { @@ -584,6 +592,7 @@ public void onError(Throwable t) { RxJavaPlugins.onError(t); } } + @Override public void onComplete() { done = 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 0633d59726..9a04a4fcc3 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java @@ -23,6 +23,7 @@ public final class ObservableFromArray extends Observable { public ObservableFromArray(T[] array) { this.array = array; } + @Override public void subscribeActual(Observer observer) { FromArrayDisposable d = new FromArrayDisposable(observer, array); @@ -101,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/ObservableFromCallable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java index 214af8f4d7..fe3c364793 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java @@ -30,6 +30,7 @@ public final class ObservableFromCallable extends Observable implements Ca public ObservableFromCallable(Callable callable) { this.callable = callable; } + @Override public void subscribeActual(Observer observer) { DeferredScalarDisposable d = new DeferredScalarDisposable(observer); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableGenerate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableGenerate.java index c74f697b7d..61f4891d1e 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableGenerate.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableGenerate.java @@ -136,7 +136,6 @@ public boolean isDisposed() { return cancelled; } - @Override public void onNext(T t) { if (!terminate) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupJoin.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupJoin.java index 3b4baaa78a..23e39af881 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupJoin.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupJoin.java @@ -88,7 +88,6 @@ interface JoinSupport { static final class GroupJoinDisposable extends AtomicInteger implements Disposable, JoinSupport { - private static final long serialVersionUID = -6071216598687999801L; final Observer downstream; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableInternalHelper.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableInternalHelper.java index 0af4a15f0a..733b18f0ee 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableInternalHelper.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableInternalHelper.java @@ -83,7 +83,6 @@ public static Function> itemDelay(final Function(itemDelay); } - static final class ObserverOnNext implements Consumer { final Observer observer; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableInterval.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableInterval.java index f786cffaba..947a3941fa 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableInterval.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableInterval.java @@ -56,7 +56,6 @@ static final class IntervalObserver extends AtomicReference implements Disposable, Runnable { - private static final long serialVersionUID = 346773832286157679L; final Observer downstream; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableIntervalRange.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableIntervalRange.java index 2e69495e66..5d48d1d3d0 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableIntervalRange.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableIntervalRange.java @@ -60,7 +60,6 @@ static final class IntervalRangeObserver extends AtomicReference implements Disposable, Runnable { - private static final long serialVersionUID = 1891866368734007884L; final Observer downstream; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableJoin.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableJoin.java index b8393e3f78..9a293ca7d5 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableJoin.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableJoin.java @@ -74,7 +74,6 @@ protected void subscribeActual(Observer observer) { static final class JoinDisposable extends AtomicInteger implements Disposable, JoinSupport { - private static final long serialVersionUID = -6071216598687999801L; final Observer downstream; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMap.java index 5df2a6c341..475963ce85 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableMap.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMap.java @@ -11,7 +11,6 @@ * the License for the specific language governing permissions and limitations under the License. */ - package io.reactivex.internal.operators.observable; import io.reactivex.*; @@ -33,7 +32,6 @@ public void subscribeActual(Observer t) { source.subscribe(new MapObserver(t, function)); } - static final class MapObserver extends BasicFuseableObserver { final Function mapper; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMapNotification.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMapNotification.java index d6b6e39f47..f4418e9927 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableMapNotification.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMapNotification.java @@ -71,7 +71,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -82,7 +81,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { ObservableSource p; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMaterialize.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMaterialize.java index 3a89194e97..9cfe82b16a 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableMaterialize.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMaterialize.java @@ -19,7 +19,6 @@ public final class ObservableMaterialize extends AbstractObservableWithUpstream> { - public ObservableMaterialize(ObservableSource source) { super(source); } @@ -46,7 +45,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); 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 f415e7016f..56de30041e 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java @@ -62,7 +62,7 @@ static final class ObserveOnObserver extends BasicIntQueueDisposable Throwable error; volatile boolean done; - volatile boolean cancelled; + volatile boolean disposed; int sourceMode; @@ -141,11 +141,11 @@ public void onComplete() { @Override public void dispose() { - if (!cancelled) { - cancelled = true; + if (!disposed) { + disposed = true; upstream.dispose(); worker.dispose(); - if (getAndIncrement() == 0) { + if (!outputFused && getAndIncrement() == 0) { queue.clear(); } } @@ -153,7 +153,7 @@ public void dispose() { @Override public boolean isDisposed() { - return cancelled; + return disposed; } void schedule() { @@ -181,6 +181,7 @@ void drainNormal() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); + disposed = true; upstream.dispose(); q.clear(); a.onError(ex); @@ -211,7 +212,7 @@ void drainFused() { int missed = 1; for (;;) { - if (cancelled) { + if (disposed) { return; } @@ -219,6 +220,7 @@ void drainFused() { Throwable ex = error; if (!delayError && d && ex != null) { + disposed = true; downstream.onError(error); worker.dispose(); return; @@ -227,6 +229,7 @@ void drainFused() { downstream.onNext(null); if (d) { + disposed = true; ex = error; if (ex != null) { downstream.onError(ex); @@ -254,7 +257,7 @@ public void run() { } boolean checkTerminated(boolean d, boolean empty, Observer a) { - if (cancelled) { + if (disposed) { queue.clear(); return true; } @@ -262,6 +265,7 @@ boolean checkTerminated(boolean d, boolean empty, Observer a) { Throwable e = error; if (delayError) { if (empty) { + disposed = true; if (e != null) { a.onError(e); } else { @@ -272,12 +276,14 @@ boolean checkTerminated(boolean d, boolean empty, Observer a) { } } else { if (e != null) { + disposed = true; queue.clear(); a.onError(e); worker.dispose(); return true; } else if (empty) { + disposed = true; a.onComplete(); worker.dispose(); return true; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturn.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturn.java index 2ed68d48e9..b91e364856 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturn.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturn.java @@ -50,7 +50,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); 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 39975af10b..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); @@ -172,6 +178,7 @@ public void onNext(T t) { inner.child.onNext(t); } } + @SuppressWarnings("unchecked") @Override public void onError(Throwable e) { @@ -185,6 +192,7 @@ public void onError(Throwable e) { RxJavaPlugins.onError(e); } } + @SuppressWarnings("unchecked") @Override public void onComplete() { 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 59b571640d..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, @@ -92,7 +91,7 @@ protected void subscribeActual(Observer observer) { void cancel(RefConnection rc) { SequentialDisposable sd; synchronized (this) { - if (connection == null) { + if (connection == null || connection != rc) { return; } long c = rc.subscriberCount - 1; @@ -113,25 +112,57 @@ void cancel(RefConnection rc) { void terminated(RefConnection rc) { synchronized (this) { - if (connection != null) { - connection = null; - if (rc.timer != null) { - rc.timer.dispose(); + if (source instanceof ObservablePublishClassic) { + if (connection != null && connection == rc) { + connection = null; + clearTimer(rc); } - if (source instanceof Disposable) { - ((Disposable)source).dispose(); + + 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) { connection = null; + Disposable connectionObject = rc.get(); DisposableHelper.dispose(rc); + if (source instanceof Disposable) { ((Disposable)source).dispose(); + } else if (source instanceof ResettableConnectable) { + if (connectionObject == null) { + rc.disconnectedEarly = true; + } else { + ((ResettableConnectable)source).resetIf(connectionObject); + } } } } @@ -150,6 +181,8 @@ static final class RefConnection extends AtomicReference boolean connected; + boolean disconnectedEarly; + RefConnection(ObservableRefCount parent) { this.parent = parent; } @@ -162,6 +195,11 @@ public void run() { @Override public void accept(Disposable t) throws Exception { DisposableHelper.replace(this, t); + synchronized (parent) { + if (disconnectedEarly) { + ((ResettableConnectable)parent.source).resetIf(t); + } + } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeat.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeat.java index 426bed7dd9..a42a8e85f6 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeat.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeat.java @@ -59,6 +59,7 @@ public void onSubscribe(Disposable d) { public void onNext(T t) { downstream.onNext(t); } + @Override public void onError(Throwable t) { downstream.onError(t); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatUntil.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatUntil.java index 8d8b9e29ac..485eb1f51f 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatUntil.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatUntil.java @@ -61,6 +61,7 @@ public void onSubscribe(Disposable d) { public void onNext(T t) { downstream.onNext(t); } + @Override public void onError(Throwable t) { downstream.onError(t); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatWhen.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatWhen.java index 4b8e194973..24725997d1 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatWhen.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatWhen.java @@ -92,7 +92,7 @@ static final class RepeatWhenObserver extends AtomicInteger implements Observ @Override public void onSubscribe(Disposable d) { - DisposableHelper.replace(this.upstream, d); + DisposableHelper.setOnce(this.upstream, d); } @Override @@ -108,6 +108,7 @@ public void onError(Throwable e) { @Override public void onComplete() { + DisposableHelper.replace(upstream, null); active = false; signaller.onNext(0); } 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 26efd9be5a..2818d975f1 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java @@ -31,7 +31,7 @@ import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Timed; -public final class ObservableReplay extends ConnectableObservable implements HasUpstreamObservableSource, Disposable { +public final class ObservableReplay extends ConnectableObservable implements HasUpstreamObservableSource, ResettableConnectable { /** The source observable. */ final ObservableSource source; /** Holds the current subscriber that is, will be or just was subscribed to the source observable. */ @@ -159,15 +159,10 @@ public ObservableSource source() { return source; } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override - public void dispose() { - current.lazySet(null); - } - - @Override - public boolean isDisposed() { - Disposable d = current.get(); - return d == null || d.isDisposed(); + public void resetIf(Disposable connectionObject) { + current.compareAndSet((ReplayObserver)connectionObject, null); } @Override @@ -371,6 +366,7 @@ public void onNext(T t) { replay(); } } + @Override public void onError(Throwable e) { // The observer front is accessed serially as required by spec so @@ -383,6 +379,7 @@ public void onError(Throwable e) { RxJavaPlugins.onError(e); } } + @Override public void onComplete() { // The observer front is accessed serially as required by spec so @@ -456,6 +453,8 @@ public void dispose() { cancelled = true; // remove this from the parent parent.remove(this); + // make sure the last known node is not retained + index = null; } } /** @@ -511,6 +510,7 @@ static final class UnboundedReplayBuffer extends ArrayList implements UnboundedReplayBuffer(int capacityHint) { super(capacityHint); } + @Override public void next(T value) { add(NotificationLite.next(value)); @@ -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. @@ -688,6 +693,7 @@ public final void replay(InnerDisposable output) { for (;;) { if (output.isDisposed()) { + output.index = null; return; } @@ -838,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; @@ -862,6 +868,7 @@ void truncate() { setFirst(prev); } } + @Override void truncateFinal() { long timeLimit = scheduler.now(unit) - maxAge; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryBiPredicate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryBiPredicate.java index a743ae55c6..f762142f0b 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryBiPredicate.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryBiPredicate.java @@ -58,13 +58,14 @@ static final class RetryBiObserver extends AtomicInteger implements Observer< @Override public void onSubscribe(Disposable d) { - upstream.update(d); + upstream.replace(d); } @Override public void onNext(T t) { downstream.onNext(t); } + @Override public void onError(Throwable t) { boolean b; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryPredicate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryPredicate.java index 3f98549c1d..ee5e074f58 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryPredicate.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryPredicate.java @@ -61,13 +61,14 @@ static final class RepeatObserver extends AtomicInteger implements Observer source, long period, TimeUnit u this.emitLast = emitLast; } - @Override public void subscribeActual(Observer t) { SerializedObserver serial = new SerializedObserver(t); 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/ObservableScan.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableScan.java index 13fd31fa1a..6b830e8d8f 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableScan.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableScan.java @@ -56,7 +56,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -67,7 +66,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableScanSeed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableScanSeed.java index 6f9222fbee..a9b4a63716 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableScanSeed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableScanSeed.java @@ -74,7 +74,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleMaybe.java index ab45f7b29f..d4e1b9a375 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleMaybe.java @@ -25,6 +25,7 @@ public final class ObservableSingleMaybe extends Maybe { public ObservableSingleMaybe(ObservableSource source) { this.source = source; } + @Override public void subscribeActual(MaybeObserver t) { source.subscribe(new SingleElementObserver(t)); @@ -51,7 +52,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -62,7 +62,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleSingle.java index 79f7aaa4b2..e1232f849f 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleSingle.java @@ -59,7 +59,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -70,7 +69,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLast.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLast.java index 3c6de3847a..2ea5953c39 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLast.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLast.java @@ -54,7 +54,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipWhile.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipWhile.java index a39b553563..d452fdaf0a 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipWhile.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipWhile.java @@ -49,7 +49,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -60,7 +59,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { if (notSkipping) { 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/ObservableTake.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTake.java index 7bda3017d5..2796357c80 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTake.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTake.java @@ -42,6 +42,7 @@ static final class TakeObserver implements Observer, Disposable { this.downstream = actual; this.remaining = limit; } + @Override public void onSubscribe(Disposable d) { if (DisposableHelper.validate(this.upstream, d)) { @@ -55,6 +56,7 @@ public void onSubscribe(Disposable d) { } } } + @Override public void onNext(T t) { if (!done && remaining-- > 0) { @@ -65,6 +67,7 @@ public void onNext(T t) { } } } + @Override public void onError(Throwable t) { if (done) { @@ -76,6 +79,7 @@ public void onError(Throwable t) { upstream.dispose(); downstream.onError(t); } + @Override public void onComplete() { if (!done) { 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/ObservableTakeUntil.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntil.java index 35e04300e0..3b831b21d4 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntil.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntil.java @@ -28,6 +28,7 @@ public ObservableTakeUntil(ObservableSource source, ObservableSource child) { TakeUntilMainObserver parent = new TakeUntilMainObserver(child); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeWhile.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeWhile.java index 1b41c6e86b..c57e6dfd12 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeWhile.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeWhile.java @@ -53,7 +53,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -64,7 +63,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleFirstTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleFirstTimed.java index 54e94ab0b7..ab1ce459f4 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleFirstTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleFirstTimed.java @@ -87,8 +87,6 @@ public void onNext(T t) { } DisposableHelper.replace(this, worker.schedule(this, timeout, unit)); } - - } @Override diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeInterval.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeInterval.java index 0cc1a2c018..7d06b18f5e 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeInterval.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeInterval.java @@ -69,7 +69,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { long now = scheduler.now(unit); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeoutTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeoutTimed.java index 7c955939c0..9e3cb8a945 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeoutTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeoutTimed.java @@ -21,6 +21,8 @@ import io.reactivex.internal.disposables.*; import io.reactivex.plugins.RxJavaPlugins; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + public final class ObservableTimeoutTimed extends AbstractObservableWithUpstream { final long timeout; final TimeUnit unit; @@ -129,7 +131,7 @@ public void onTimeout(long idx) { if (compareAndSet(idx, Long.MAX_VALUE)) { DisposableHelper.dispose(upstream); - downstream.onError(new TimeoutException()); + downstream.onError(new TimeoutException(timeoutMessage(timeout, unit))); worker.dispose(); } @@ -147,7 +149,6 @@ public boolean isDisposed() { } } - static final class TimeoutTask implements Runnable { final TimeoutSupport parent; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableToList.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableToList.java index 53dd99fd2b..afeee5399d 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableToList.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableToList.java @@ -71,7 +71,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -82,7 +81,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { collection.add(t); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableToListSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableToListSingle.java index 4358229f53..410af1e161 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableToListSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableToListSingle.java @@ -83,7 +83,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -94,7 +93,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { collection.add(t); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySelector.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySelector.java index b15d4ee140..d8e745e213 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySelector.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySelector.java @@ -69,6 +69,8 @@ static final class WindowBoundaryMainObserver final AtomicLong windows = new AtomicLong(); + final AtomicBoolean stopWindows = new AtomicBoolean(); + WindowBoundaryMainObserver(Observer> actual, ObservableSource open, Function> close, int bufferSize) { super(actual, new MpscLinkedQueue()); @@ -87,14 +89,13 @@ public void onSubscribe(Disposable d) { downstream.onSubscribe(this); - if (cancelled) { + if (stopWindows.get()) { return; } OperatorWindowBoundaryOpenObserver os = new OperatorWindowBoundaryOpenObserver(this); if (boundary.compareAndSet(null, os)) { - windows.getAndIncrement(); open.subscribe(os); } } @@ -164,12 +165,17 @@ void error(Throwable t) { @Override public void dispose() { - cancelled = true; + if (stopWindows.compareAndSet(false, true)) { + DisposableHelper.dispose(boundary); + if (windows.decrementAndGet() == 0) { + upstream.dispose(); + } + } } @Override public boolean isDisposed() { - return cancelled; + return stopWindows.get(); } void disposeBoundary() { @@ -229,11 +235,10 @@ void drainLoop() { continue; } - if (cancelled) { + if (stopWindows.get()) { continue; } - w = UnicastSubject.create(bufferSize); ws.add(w); @@ -245,7 +250,7 @@ void drainLoop() { p = ObjectHelper.requireNonNull(close.apply(wo.open), "The ObservableSource supplied is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - cancelled = true; + stopWindows.set(true); a.onError(e); 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 5b76d067e7..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; } @@ -264,10 +256,9 @@ static final class WindowExactBoundedObserver UnicastSubject window; - volatile boolean terminated; - final AtomicReference timer = new AtomicReference(); + final SequentialDisposable timer = new SequentialDisposable(); WindowExactBoundedObserver( Observer> actual, @@ -313,7 +304,7 @@ public void onSubscribe(Disposable d) { task = scheduler.schedulePeriodicallyDirect(consumerIndexHolder, timespan, timespan, unit); } - DisposableHelper.replace(timer, task); + timer.replace(task); } } @@ -371,7 +362,6 @@ public void onError(Throwable t) { } downstream.onError(t); - disposeTimer(); } @Override @@ -382,7 +372,6 @@ public void onComplete() { } downstream.onComplete(); - disposeTimer(); } @Override @@ -429,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; } @@ -445,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); @@ -508,7 +497,6 @@ public void run() { p.queue.offer(this); } else { p.terminated = true; - p.disposeTimer(); } if (p.enter()) { p.drainLoop(); @@ -593,7 +581,6 @@ public void onError(Throwable t) { } downstream.onError(t); - disposeWorker(); } @Override @@ -604,7 +591,6 @@ public void onComplete() { } downstream.onComplete(); - disposeWorker(); } @Override @@ -617,10 +603,6 @@ public boolean isDisposed() { return cancelled; } - void disposeWorker() { - worker.dispose(); - } - void complete(UnicastSubject w) { queue.offer(new SubjectWork(w, false)); if (enter()) { @@ -641,9 +623,9 @@ void drainLoop() { for (;;) { if (terminated) { upstream.dispose(); - disposeWorker(); q.clear(); ws.clear(); + worker.dispose(); return; } @@ -666,8 +648,8 @@ void drainLoop() { w.onComplete(); } } - disposeWorker(); ws.clear(); + worker.dispose(); return; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFrom.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFrom.java index 9bc7984dd6..83369a73a7 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFrom.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFrom.java @@ -61,6 +61,7 @@ static final class WithLatestFromObserver extends AtomicReference im this.downstream = actual; this.combiner = combiner; } + @Override public void onSubscribe(Disposable d) { DisposableHelper.setOnce(this.upstream, d); 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 5ac0027aeb..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; @@ -265,6 +269,7 @@ static final class ZipObserver implements Observer { this.parent = parent; this.queue = new SpscLinkedArrayQueue(bufferSize); } + @Override public void onSubscribe(Disposable d) { DisposableHelper.setOnce(this.upstream, d); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableZipIterable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableZipIterable.java index 8db76ed428..22b7b95196 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableZipIterable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableZipIterable.java @@ -90,7 +90,6 @@ public void onSubscribe(Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -101,7 +100,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelCollect.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelCollect.java index 4f7b53ed94..b1052b2fd6 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelCollect.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelCollect.java @@ -87,7 +87,6 @@ public int parallelism() { static final class ParallelCollectSubscriber extends DeferredScalarSubscriber { - private static final long serialVersionUID = -4767392946044436228L; final BiConsumer collector; diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromPublisher.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromPublisher.java index fc7288a1a1..33d83ca34c 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromPublisher.java @@ -62,7 +62,6 @@ static final class ParallelDispatcher extends AtomicInteger implements FlowableSubscriber { - private static final long serialVersionUID = -4470634016609963609L; final Subscriber[] subscribers; diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java index b248cde69f..975c151729 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java @@ -110,15 +110,13 @@ public void cancel() { } void cancelAll() { - for (int i = 0; i < subscribers.length; i++) { - JoinInnerSubscriber s = subscribers[i]; + for (JoinInnerSubscriber s : subscribers) { s.cancel(); } } void cleanup() { - for (int i = 0; i < subscribers.length; i++) { - JoinInnerSubscriber s = subscribers[i]; + for (JoinInnerSubscriber s : subscribers) { s.queue = null; } } diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduce.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduce.java index 8e421ef112..0c3bcfbba1 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduce.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduce.java @@ -86,7 +86,6 @@ public int parallelism() { static final class ParallelReduceSubscriber extends DeferredScalarSubscriber { - private static final long serialVersionUID = 8200530050639449080L; final BiFunction reducer; diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java index 347ce2ead9..8757752322 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java @@ -52,7 +52,6 @@ protected void subscribeActual(Subscriber s) { static final class ParallelReduceFullMainSubscriber extends DeferredScalarSubscription { - private static final long serialVersionUID = -5370107872170712765L; final ParallelReduceFullInnerSubscriber[] subscribers; diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java index 1151716fff..a7c4947709 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java @@ -266,7 +266,6 @@ static final class SortedJoinInnerSubscriber extends AtomicReference implements FlowableSubscriber> { - private static final long serialVersionUID = 6751017204873808094L; final SortedJoinSubscription parent; diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleAmb.java b/src/main/java/io/reactivex/internal/operators/single/SingleAmb.java index d7508c3a72..2584506b59 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleAmb.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleAmb.java @@ -59,21 +59,21 @@ protected void subscribeActual(final SingleObserver observer) { count = sources.length; } + final AtomicBoolean winner = new AtomicBoolean(); final CompositeDisposable set = new CompositeDisposable(); - AmbSingleObserver shared = new AmbSingleObserver(observer, set); observer.onSubscribe(set); for (int i = 0; i < count; i++) { SingleSource s1 = sources[i]; - if (shared.get()) { + if (set.isDisposed()) { return; } if (s1 == null) { set.dispose(); Throwable e = new NullPointerException("One of the sources is null"); - if (shared.compareAndSet(false, true)) { + if (winner.compareAndSet(false, true)) { observer.onError(e); } else { RxJavaPlugins.onError(e); @@ -81,31 +81,36 @@ protected void subscribeActual(final SingleObserver observer) { return; } - s1.subscribe(shared); + s1.subscribe(new AmbSingleObserver(observer, set, winner)); } } - static final class AmbSingleObserver extends AtomicBoolean implements SingleObserver { - - private static final long serialVersionUID = -1944085461036028108L; + static final class AmbSingleObserver implements SingleObserver { final CompositeDisposable set; final SingleObserver downstream; - AmbSingleObserver(SingleObserver observer, CompositeDisposable set) { + final AtomicBoolean winner; + + Disposable upstream; + + AmbSingleObserver(SingleObserver observer, CompositeDisposable set, AtomicBoolean winner) { this.downstream = observer; this.set = set; + this.winner = winner; } @Override public void onSubscribe(Disposable d) { + this.upstream = d; set.add(d); } @Override public void onSuccess(T value) { - if (compareAndSet(false, true)) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); downstream.onSuccess(value); } @@ -113,7 +118,8 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - if (compareAndSet(false, true)) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); downstream.onError(e); } else { diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithCompletable.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithCompletable.java index ba5beeec9d..86dbd4d39f 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithCompletable.java @@ -40,7 +40,6 @@ static final class OtherObserver extends AtomicReference implements CompletableObserver, Disposable { - private static final long serialVersionUID = -8565274649390031272L; final SingleObserver downstream; diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithObservable.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithObservable.java index 0614c091c0..c905bba0c6 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithObservable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithObservable.java @@ -41,7 +41,6 @@ static final class OtherSubscriber extends AtomicReference implements Observer, Disposable { - private static final long serialVersionUID = -8565274649390031272L; final SingleObserver downstream; diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithPublisher.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithPublisher.java index bbc8a9e460..ee93007020 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithPublisher.java @@ -44,7 +44,6 @@ static final class OtherSubscriber extends AtomicReference implements FlowableSubscriber, Disposable { - private static final long serialVersionUID = -8565274649390031272L; final SingleObserver downstream; diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java index 1c1b4aabe3..83255b95c3 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java @@ -40,7 +40,6 @@ static final class OtherObserver extends AtomicReference implements SingleObserver, Disposable { - private static final long serialVersionUID = -8565274649390031272L; final SingleObserver downstream; diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDematerialize.java b/src/main/java/io/reactivex/internal/operators/single/SingleDematerialize.java new file mode 100644 index 0000000000..2e402b05da --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDematerialize.java @@ -0,0 +1,105 @@ +/** + * 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.single; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps the success value of the source to a Notification, then + * maps it back to the corresponding signal type. + * @param the element type of the source + * @param the element type of the Notification and result + * @since 2.2.4 - experimental + */ +@Experimental +public final class SingleDematerialize extends Maybe { + + final Single source; + + final Function> selector; + + public SingleDematerialize(Single source, Function> selector) { + this.source = source; + this.selector = selector; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new DematerializeObserver(observer, selector)); + } + + static final class DematerializeObserver implements SingleObserver, Disposable { + + final MaybeObserver downstream; + + final Function> selector; + + Disposable upstream; + + DematerializeObserver(MaybeObserver downstream, + Function> selector) { + this.downstream = downstream; + this.selector = selector; + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T t) { + Notification notification; + + try { + notification = ObjectHelper.requireNonNull(selector.apply(t), "The selector returned a null Notification"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + if (notification.isOnNext()) { + downstream.onSuccess(notification.getValue()); + } else if (notification.isOnComplete()) { + downstream.onComplete(); + } else { + downstream.onError(notification.getError()); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnTerminate.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnTerminate.java new file mode 100644 index 0000000000..7497aff902 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnTerminate.java @@ -0,0 +1,78 @@ +/** + * 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.single; + +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; + +public final class SingleDoOnTerminate extends Single { + + final SingleSource source; + + final Action onTerminate; + + public SingleDoOnTerminate(SingleSource source, Action onTerminate) { + this.source = source; + this.onTerminate = onTerminate; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + source.subscribe(new DoOnTerminate(observer)); + } + + final class DoOnTerminate implements SingleObserver { + + final SingleObserver downstream; + + DoOnTerminate(SingleObserver observer) { + this.downstream = observer; + } + + @Override + public void onSubscribe(Disposable d) { + downstream.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleEquals.java b/src/main/java/io/reactivex/internal/operators/single/SingleEquals.java index 83c40b1416..07d3f36602 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleEquals.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleEquals.java @@ -57,6 +57,7 @@ static class InnerObserver implements SingleObserver { this.downstream = observer; this.count = count; } + @Override public void onSubscribe(Disposable d) { set.add(d); diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowable.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowable.java index 1243413dff..f6f4c79cf5 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowable.java @@ -235,7 +235,6 @@ void slowPath(Subscriber a, Iterator iterator) { return; } - boolean b; try { diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservable.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservable.java index 760a052278..b3f822cc91 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservable.java @@ -126,7 +126,6 @@ public void onSuccess(T value) { return; } - boolean b; try { diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleInternalHelper.java b/src/main/java/io/reactivex/internal/operators/single/SingleInternalHelper.java index f4ed04d151..7770fd38b4 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleInternalHelper.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleInternalHelper.java @@ -110,6 +110,7 @@ public Observable apply(SingleSource v) { return new SingleToObservable(v); } } + @SuppressWarnings({ "rawtypes", "unchecked" }) public static Function, Observable> toObservable() { return (Function)ToObservable.INSTANCE; diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleMaterialize.java b/src/main/java/io/reactivex/internal/operators/single/SingleMaterialize.java new file mode 100644 index 0000000000..e22b64865d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleMaterialize.java @@ -0,0 +1,40 @@ +/** + * 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.single; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.internal.operators.mixed.MaterializeSingleObserver; + +/** + * Turn the signal types of a Single source into a single Notification of + * equal kind. + * + * @param the element type of the source + * @since 2.2.4 - experimental + */ +@Experimental +public final class SingleMaterialize extends Single> { + + final Single source; + + public SingleMaterialize(Single source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver> observer) { + source.subscribe(new MaterializeSingleObserver(observer)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleOnErrorReturn.java b/src/main/java/io/reactivex/internal/operators/single/SingleOnErrorReturn.java index 3f1b0588cf..0ae695a3e8 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleOnErrorReturn.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleOnErrorReturn.java @@ -32,8 +32,6 @@ public SingleOnErrorReturn(SingleSource source, this.value = value; } - - @Override protected void subscribeActual(final SingleObserver observer) { diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleTimeout.java b/src/main/java/io/reactivex/internal/operators/single/SingleTimeout.java index 4c97882d62..ea4212f0ec 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleTimeout.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleTimeout.java @@ -21,6 +21,8 @@ import io.reactivex.internal.disposables.DisposableHelper; import io.reactivex.plugins.RxJavaPlugins; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + public final class SingleTimeout extends Single { final SingleSource source; @@ -45,7 +47,7 @@ public SingleTimeout(SingleSource source, long timeout, TimeUnit unit, Schedu @Override protected void subscribeActual(final SingleObserver observer) { - TimeoutMainObserver parent = new TimeoutMainObserver(observer, other); + TimeoutMainObserver parent = new TimeoutMainObserver(observer, other, timeout, unit); observer.onSubscribe(parent); DisposableHelper.replace(parent.task, scheduler.scheduleDirect(parent, timeout, unit)); @@ -66,6 +68,10 @@ static final class TimeoutMainObserver extends AtomicReference SingleSource other; + final long timeout; + + final TimeUnit unit; + static final class TimeoutFallbackObserver extends AtomicReference implements SingleObserver { @@ -92,9 +98,11 @@ public void onError(Throwable e) { } } - TimeoutMainObserver(SingleObserver actual, SingleSource other) { + TimeoutMainObserver(SingleObserver actual, SingleSource other, long timeout, TimeUnit unit) { this.downstream = actual; this.other = other; + this.timeout = timeout; + this.unit = unit; this.task = new AtomicReference(); if (other != null) { this.fallback = new TimeoutFallbackObserver(actual); @@ -112,7 +120,7 @@ public void run() { } SingleSource other = this.other; if (other == null) { - downstream.onError(new TimeoutException()); + downstream.onError(new TimeoutException(timeoutMessage(timeout, unit))); } else { this.other = null; other.subscribe(fallback); diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleToFlowable.java b/src/main/java/io/reactivex/internal/operators/single/SingleToFlowable.java index f48aa4659b..de2b8a9ed8 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleToFlowable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleToFlowable.java @@ -40,7 +40,6 @@ public void subscribeActual(final Subscriber s) { static final class SingleToFlowableObserver extends DeferredScalarSubscription implements SingleObserver { - private static final long serialVersionUID = 187782011903685568L; Disposable upstream; diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleZipArray.java b/src/main/java/io/reactivex/internal/operators/single/SingleZipArray.java index e9ba27d755..31fd470ecd 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleZipArray.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleZipArray.java @@ -39,7 +39,6 @@ protected void subscribeActual(SingleObserver observer) { SingleSource[] sources = this.sources; int n = sources.length; - if (n == 1) { sources[0].subscribe(new SingleMap.MapSingleObserver(observer, new SingletonArrayFunc())); return; @@ -67,7 +66,6 @@ protected void subscribeActual(SingleObserver observer) { static final class ZipCoordinator extends AtomicInteger implements Disposable { - private static final long serialVersionUID = -5556924161382950569L; final SingleObserver downstream; diff --git a/src/main/java/io/reactivex/internal/queue/MpscLinkedQueue.java b/src/main/java/io/reactivex/internal/queue/MpscLinkedQueue.java index eddfb306b4..c33147de06 100644 --- a/src/main/java/io/reactivex/internal/queue/MpscLinkedQueue.java +++ b/src/main/java/io/reactivex/internal/queue/MpscLinkedQueue.java @@ -36,7 +36,7 @@ public MpscLinkedQueue() { consumerNode = new AtomicReference>(); LinkedQueueNode node = new LinkedQueueNode(); spConsumerNode(node); - xchgProducerNode(node);// this ensures correct construction: StoreLoad + xchgProducerNode(node); // this ensures correct construction: StoreLoad } /** diff --git a/src/main/java/io/reactivex/internal/queue/SpscArrayQueue.java b/src/main/java/io/reactivex/internal/queue/SpscArrayQueue.java index 1afa99738f..53ac212600 100644 --- a/src/main/java/io/reactivex/internal/queue/SpscArrayQueue.java +++ b/src/main/java/io/reactivex/internal/queue/SpscArrayQueue.java @@ -89,12 +89,12 @@ public E poll() { final long index = consumerIndex.get(); final int offset = calcElementOffset(index); // local load of field to avoid repeated loads after volatile reads - final E e = lvElement(offset);// LoadLoad + final E e = lvElement(offset); // LoadLoad if (null == e) { return null; } soConsumerIndex(index + 1); // ordered store -> atomic and ordered for size() - soElement(offset, null);// StoreStore + soElement(offset, null); // StoreStore return e; } diff --git a/src/main/java/io/reactivex/internal/queue/SpscLinkedArrayQueue.java b/src/main/java/io/reactivex/internal/queue/SpscLinkedArrayQueue.java index 35c86222cd..e5e71e29f4 100644 --- a/src/main/java/io/reactivex/internal/queue/SpscLinkedArrayQueue.java +++ b/src/main/java/io/reactivex/internal/queue/SpscLinkedArrayQueue.java @@ -92,8 +92,8 @@ public boolean offer(final T e) { } private boolean writeToQueue(final AtomicReferenceArray buffer, final T e, final long index, final int offset) { - soElement(buffer, offset, e);// StoreStore - soProducerIndex(index + 1);// this ensures atomic write of long on 32bit platforms + soElement(buffer, offset, e); // StoreStore + soProducerIndex(index + 1); // this ensures atomic write of long on 32bit platforms return true; } @@ -103,16 +103,17 @@ private void resize(final AtomicReferenceArray oldBuffer, final long cur final AtomicReferenceArray newBuffer = new AtomicReferenceArray(capacity); producerBuffer = newBuffer; producerLookAhead = currIndex + mask - 1; - soElement(newBuffer, offset, e);// StoreStore + soElement(newBuffer, offset, e); // StoreStore soNext(oldBuffer, newBuffer); soElement(oldBuffer, offset, HAS_NEXT); // new buffer is visible after element is // inserted - soProducerIndex(currIndex + 1);// this ensures correctness on 32bit platforms + soProducerIndex(currIndex + 1); // this ensures correctness on 32bit platforms } private void soNext(AtomicReferenceArray curr, AtomicReferenceArray next) { soElement(curr, calcDirectOffset(curr.length() - 1), next); } + @SuppressWarnings("unchecked") private AtomicReferenceArray lvNextBufferAndUnlink(AtomicReferenceArray curr, int nextIndex) { int nextOffset = calcDirectOffset(nextIndex); @@ -134,11 +135,11 @@ public T poll() { final long index = lpConsumerIndex(); final int mask = consumerMask; final int offset = calcWrappedOffset(index, mask); - final Object e = lvElement(buffer, offset);// LoadLoad + final Object e = lvElement(buffer, offset); // LoadLoad boolean isNextBuffer = e == HAS_NEXT; if (null != e && !isNextBuffer) { - soElement(buffer, offset, null);// StoreStore - soConsumerIndex(index + 1);// this ensures correctness on 32bit platforms + soElement(buffer, offset, null); // StoreStore + soConsumerIndex(index + 1); // this ensures correctness on 32bit platforms return (T) e; } else if (isNextBuffer) { return newBufferPoll(lvNextBufferAndUnlink(buffer, mask + 1), index, mask); @@ -151,10 +152,10 @@ public T poll() { private T newBufferPoll(AtomicReferenceArray nextBuffer, final long index, final int mask) { consumerBuffer = nextBuffer; final int offsetInNew = calcWrappedOffset(index, mask); - final T n = (T) lvElement(nextBuffer, offsetInNew);// LoadLoad + final T n = (T) lvElement(nextBuffer, offsetInNew); // LoadLoad if (null != n) { - soElement(nextBuffer, offsetInNew, null);// StoreStore - soConsumerIndex(index + 1);// this ensures correctness on 32bit platforms + soElement(nextBuffer, offsetInNew, null); // StoreStore + soConsumerIndex(index + 1); // this ensures correctness on 32bit platforms } return n; } @@ -165,7 +166,7 @@ public T peek() { final long index = lpConsumerIndex(); final int mask = consumerMask; final int offset = calcWrappedOffset(index, mask); - final Object e = lvElement(buffer, offset);// LoadLoad + final Object e = lvElement(buffer, offset); // LoadLoad if (e == HAS_NEXT) { return newBufferPeek(lvNextBufferAndUnlink(buffer, mask + 1), index, mask); } @@ -177,8 +178,9 @@ public T peek() { private T newBufferPeek(AtomicReferenceArray nextBuffer, final long index, final int mask) { consumerBuffer = nextBuffer; final int offsetInNew = calcWrappedOffset(index, mask); - return (T) lvElement(nextBuffer, offsetInNew);// LoadLoad + return (T) lvElement(nextBuffer, offsetInNew); // LoadLoad } + @Override public void clear() { while (poll() != null || !isEmpty()) { } // NOPMD @@ -275,13 +277,13 @@ public boolean offer(T first, T second) { producerBuffer = newBuffer; pi = calcWrappedOffset(p, m); - soElement(newBuffer, pi + 1, second);// StoreStore + soElement(newBuffer, pi + 1, second); // StoreStore soElement(newBuffer, pi, first); soNext(buffer, newBuffer); soElement(buffer, pi, HAS_NEXT); // new buffer is visible after element is - soProducerIndex(p + 2);// this ensures correctness on 32bit platforms + soProducerIndex(p + 2); // this ensures correctness on 32bit platforms } return true; diff --git a/src/main/java/io/reactivex/internal/schedulers/ComputationScheduler.java b/src/main/java/io/reactivex/internal/schedulers/ComputationScheduler.java index dda22b9255..6f10ab2867 100644 --- a/src/main/java/io/reactivex/internal/schedulers/ComputationScheduler.java +++ b/src/main/java/io/reactivex/internal/schedulers/ComputationScheduler.java @@ -187,7 +187,6 @@ public void shutdown() { } } - static final class EventLoopWorker extends Scheduler.Worker { private final ListCompositeDisposable serial; private final CompositeDisposable timed; @@ -227,6 +226,7 @@ public Disposable schedule(@NonNull Runnable action) { return poolWorker.scheduleActual(action, 0, TimeUnit.MILLISECONDS, serial); } + @NonNull @Override public Disposable schedule(@NonNull Runnable action, long delayTime, @NonNull TimeUnit unit) { diff --git a/src/main/java/io/reactivex/internal/schedulers/ExecutorScheduler.java b/src/main/java/io/reactivex/internal/schedulers/ExecutorScheduler.java index b18e18d16d..75322a8de8 100644 --- a/src/main/java/io/reactivex/internal/schedulers/ExecutorScheduler.java +++ b/src/main/java/io/reactivex/internal/schedulers/ExecutorScheduler.java @@ -22,7 +22,7 @@ import io.reactivex.internal.disposables.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.queue.MpscLinkedQueue; -import io.reactivex.internal.schedulers.ExecutorScheduler.ExecutorWorker.BooleanRunnable; +import io.reactivex.internal.schedulers.ExecutorScheduler.ExecutorWorker.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.*; @@ -31,19 +31,22 @@ */ public final class ExecutorScheduler extends Scheduler { + final boolean interruptibleWorker; + @NonNull final Executor executor; static final Scheduler HELPER = Schedulers.single(); - public ExecutorScheduler(@NonNull Executor executor) { + public ExecutorScheduler(@NonNull Executor executor, boolean interruptibleWorker) { this.executor = executor; + this.interruptibleWorker = interruptibleWorker; } @NonNull @Override public Worker createWorker() { - return new ExecutorWorker(executor); + return new ExecutorWorker(executor, interruptibleWorker); } @NonNull @@ -58,9 +61,15 @@ public Disposable scheduleDirect(@NonNull Runnable run) { return task; } - BooleanRunnable br = new BooleanRunnable(decoratedRun); - executor.execute(br); - return br; + if (interruptibleWorker) { + InterruptibleRunnable interruptibleTask = new InterruptibleRunnable(decoratedRun, null); + executor.execute(interruptibleTask); + return interruptibleTask; + } else { + BooleanRunnable br = new BooleanRunnable(decoratedRun); + executor.execute(br); + return br; + } } catch (RejectedExecutionException ex) { RxJavaPlugins.onError(ex); return EmptyDisposable.INSTANCE; @@ -111,6 +120,9 @@ public Disposable schedulePeriodicallyDirect(@NonNull Runnable run, long initial } /* public: test support. */ public static final class ExecutorWorker extends Scheduler.Worker implements Runnable { + + final boolean interruptibleWorker; + final Executor executor; final MpscLinkedQueue queue; @@ -121,9 +133,10 @@ public static final class ExecutorWorker extends Scheduler.Worker implements Run final CompositeDisposable tasks = new CompositeDisposable(); - public ExecutorWorker(Executor executor) { + public ExecutorWorker(Executor executor, boolean interruptibleWorker) { this.executor = executor; this.queue = new MpscLinkedQueue(); + this.interruptibleWorker = interruptibleWorker; } @NonNull @@ -134,9 +147,24 @@ public Disposable schedule(@NonNull Runnable run) { } Runnable decoratedRun = RxJavaPlugins.onSchedule(run); - BooleanRunnable br = new BooleanRunnable(decoratedRun); - queue.offer(br); + Runnable task; + Disposable disposable; + + if (interruptibleWorker) { + InterruptibleRunnable interruptibleTask = new InterruptibleRunnable(decoratedRun, tasks); + tasks.add(interruptibleTask); + + task = interruptibleTask; + disposable = interruptibleTask; + } else { + BooleanRunnable runnableTask = new BooleanRunnable(decoratedRun); + + task = runnableTask; + disposable = runnableTask; + } + + queue.offer(task); if (wip.getAndIncrement() == 0) { try { @@ -149,7 +177,7 @@ public Disposable schedule(@NonNull Runnable run) { } } - return br; + return disposable; } @NonNull @@ -162,7 +190,6 @@ public Disposable schedule(@NonNull Runnable run, long delay, @NonNull TimeUnit return EmptyDisposable.INSTANCE; } - SequentialDisposable first = new SequentialDisposable(); final SequentialDisposable mar = new SequentialDisposable(first); @@ -289,6 +316,97 @@ public void run() { mar.replace(schedule(decoratedRun)); } } + + /** + * Wrapper for a {@link Runnable} with additional logic for handling interruption on + * a shared thread, similar to how Java Executors do it. + */ + static final class InterruptibleRunnable extends AtomicInteger implements Runnable, Disposable { + + private static final long serialVersionUID = -3603436687413320876L; + + final Runnable run; + + final DisposableContainer tasks; + + volatile Thread thread; + + static final int READY = 0; + + static final int RUNNING = 1; + + static final int FINISHED = 2; + + static final int INTERRUPTING = 3; + + static final int INTERRUPTED = 4; + + InterruptibleRunnable(Runnable run, DisposableContainer tasks) { + this.run = run; + this.tasks = tasks; + } + + @Override + public void run() { + if (get() == READY) { + thread = Thread.currentThread(); + if (compareAndSet(READY, RUNNING)) { + try { + run.run(); + } finally { + thread = null; + if (compareAndSet(RUNNING, FINISHED)) { + cleanup(); + } else { + while (get() == INTERRUPTING) { + Thread.yield(); + } + Thread.interrupted(); + } + } + } else { + thread = null; + } + } + } + + @Override + public void dispose() { + for (;;) { + int state = get(); + if (state >= FINISHED) { + break; + } else if (state == READY) { + if (compareAndSet(READY, INTERRUPTED)) { + cleanup(); + break; + } + } else { + if (compareAndSet(RUNNING, INTERRUPTING)) { + Thread t = thread; + if (t != null) { + t.interrupt(); + thread = null; + } + set(INTERRUPTED); + cleanup(); + break; + } + } + } + } + + void cleanup() { + if (tasks != null) { + tasks.delete(this); + } + } + + @Override + public boolean isDisposed() { + return get() >= FINISHED; + } + } } static final class DelayedRunnable extends AtomicReference diff --git a/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java b/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java index f6913020a5..02cb44a1d3 100644 --- a/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java +++ b/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java @@ -34,7 +34,11 @@ public final class IoScheduler extends Scheduler { private static final String EVICTOR_THREAD_NAME_PREFIX = "RxCachedWorkerPoolEvictor"; static final RxThreadFactory EVICTOR_THREAD_FACTORY; - private static final long KEEP_ALIVE_TIME = 60; + /** The name of the system property for setting the keep-alive time (in seconds) for this Scheduler workers. */ + private static final String KEY_KEEP_ALIVE_TIME = "rx2.io-keep-alive-time"; + public static final long KEEP_ALIVE_TIME_DEFAULT = 60; + + private static final long KEEP_ALIVE_TIME; private static final TimeUnit KEEP_ALIVE_UNIT = TimeUnit.SECONDS; static final ThreadWorker SHUTDOWN_THREAD_WORKER; @@ -44,8 +48,15 @@ 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 { + KEEP_ALIVE_TIME = Long.getLong(KEY_KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_DEFAULT); + SHUTDOWN_THREAD_WORKER = new ThreadWorker(new RxThreadFactory("RxCachedThreadSchedulerShutdown")); SHUTDOWN_THREAD_WORKER.dispose(); @@ -56,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(); } @@ -151,6 +164,7 @@ public IoScheduler() { } /** + * Constructs an IoScheduler with the given thread factory and starts the pool of workers. * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. */ @@ -167,6 +181,7 @@ public void start() { update.shutdown(); } } + @Override public void shutdown() { for (;;) { @@ -191,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; @@ -208,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/NewThreadWorker.java b/src/main/java/io/reactivex/internal/schedulers/NewThreadWorker.java index 3499168f71..9ef225aceb 100644 --- a/src/main/java/io/reactivex/internal/schedulers/NewThreadWorker.java +++ b/src/main/java/io/reactivex/internal/schedulers/NewThreadWorker.java @@ -116,7 +116,6 @@ public Disposable schedulePeriodicallyDirect(Runnable run, long initialDelay, lo } } - /** * Wraps the given runnable into a ScheduledRunnable and schedules it * on the underlying ScheduledExecutorService. 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/schedulers/SingleScheduler.java b/src/main/java/io/reactivex/internal/schedulers/SingleScheduler.java index d65348aa3c..40a7ce0b85 100644 --- a/src/main/java/io/reactivex/internal/schedulers/SingleScheduler.java +++ b/src/main/java/io/reactivex/internal/schedulers/SingleScheduler.java @@ -53,6 +53,8 @@ public SingleScheduler() { } /** + * Constructs a SingleScheduler with the given ThreadFactory and prepares the + * single scheduler thread. * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. */ diff --git a/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java index c296ffe088..5e15bea285 100644 --- a/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java @@ -28,7 +28,6 @@ public final class ForEachWhileSubscriber extends AtomicReference implements FlowableSubscriber, Disposable { - private static final long serialVersionUID = -4403180040475402120L; final Predicate onNext; @@ -109,6 +108,6 @@ public void dispose() { @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(this.get()); + return this.get() == SubscriptionHelper.CANCELLED; } } diff --git a/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java index 1cc2eb2e09..4b2c329c97 100644 --- a/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java @@ -24,6 +24,8 @@ import io.reactivex.internal.util.BlockingHelper; import io.reactivex.plugins.RxJavaPlugins; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + /** * A Subscriber + Future that expects exactly one upstream value and provides it * via the (blocking) Future API. @@ -63,7 +65,7 @@ public boolean cancel(boolean mayInterruptIfRunning) { @Override public boolean isCancelled() { - return SubscriptionHelper.isCancelled(upstream.get()); + return upstream.get() == SubscriptionHelper.CANCELLED; } @Override @@ -93,7 +95,7 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException, Execution if (getCount() != 0) { BlockingHelper.verifyNonBlocking(); if (!await(timeout, unit)) { - throw new TimeoutException(); + throw new TimeoutException(timeoutMessage(timeout, unit)); } } diff --git a/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriber.java index fc6647a83b..70ea26740e 100644 --- a/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriber.java @@ -32,7 +32,6 @@ public final class InnerQueuedSubscriber extends AtomicReference implements FlowableSubscriber, Subscription { - private static final long serialVersionUID = 22876611072430776L; final InnerQueuedSubscriberSupport parent; 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/internal/subscriptions/BasicIntQueueSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/BasicIntQueueSubscription.java index eea08d07e7..a3d2259fd9 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/BasicIntQueueSubscription.java +++ b/src/main/java/io/reactivex/internal/subscriptions/BasicIntQueueSubscription.java @@ -24,7 +24,6 @@ */ public abstract class BasicIntQueueSubscription extends AtomicInteger implements QueueSubscription { - private static final long serialVersionUID = -6671519529404341862L; @Override diff --git a/src/main/java/io/reactivex/internal/subscriptions/BasicQueueSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/BasicQueueSubscription.java index 8776e8c5a3..ebb9935ed8 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/BasicQueueSubscription.java +++ b/src/main/java/io/reactivex/internal/subscriptions/BasicQueueSubscription.java @@ -24,7 +24,6 @@ */ public abstract class BasicQueueSubscription extends AtomicLong implements QueueSubscription { - private static final long serialVersionUID = -6671519529404341862L; @Override diff --git a/src/main/java/io/reactivex/internal/subscriptions/DeferredScalarSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/DeferredScalarSubscription.java index 536ddf4c00..131d1d2342 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/DeferredScalarSubscription.java +++ b/src/main/java/io/reactivex/internal/subscriptions/DeferredScalarSubscription.java @@ -34,7 +34,6 @@ */ public class DeferredScalarSubscription extends BasicIntQueueSubscription { - private static final long serialVersionUID = -2151279923272604993L; /** The Subscriber to emit the value to. */ diff --git a/src/main/java/io/reactivex/internal/subscriptions/EmptySubscription.java b/src/main/java/io/reactivex/internal/subscriptions/EmptySubscription.java index 08fd1faae8..d4d78f3279 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/EmptySubscription.java +++ b/src/main/java/io/reactivex/internal/subscriptions/EmptySubscription.java @@ -29,6 +29,7 @@ public enum EmptySubscription implements QueueSubscription { public void request(long n) { SubscriptionHelper.validate(n); } + @Override public void cancel() { // no-op @@ -67,27 +68,33 @@ public static void complete(Subscriber s) { s.onSubscribe(INSTANCE); s.onComplete(); } + @Nullable @Override public Object poll() { return null; // always empty } + @Override public boolean isEmpty() { return true; } + @Override public void clear() { // nothing to do } + @Override public int requestFusion(int mode) { return mode & ASYNC; // accept async mode: an onComplete or onError will be signalled after anyway } + @Override public boolean offer(Object value) { throw new UnsupportedOperationException("Should not be called!"); } + @Override public boolean offer(Object v1, Object v2) { throw new UnsupportedOperationException("Should not be called!"); diff --git a/src/main/java/io/reactivex/internal/subscriptions/SubscriptionArbiter.java b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionArbiter.java index 6abda2ce51..2796573392 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/SubscriptionArbiter.java +++ b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionArbiter.java @@ -55,11 +55,14 @@ public class SubscriptionArbiter extends AtomicInteger implements Subscription { final AtomicLong missedProduced; + final boolean cancelOnReplace; + volatile boolean cancelled; protected boolean unbounded; - public SubscriptionArbiter() { + public SubscriptionArbiter(boolean cancelOnReplace) { + this.cancelOnReplace = cancelOnReplace; missedSubscription = new AtomicReference(); missedRequested = new AtomicLong(); missedProduced = new AtomicLong(); @@ -80,7 +83,7 @@ public final void setSubscription(Subscription s) { if (get() == 0 && compareAndSet(0, 1)) { Subscription a = actual; - if (a != null) { + if (a != null && cancelOnReplace) { a.cancel(); } @@ -100,7 +103,7 @@ public final void setSubscription(Subscription s) { } Subscription a = missedSubscription.getAndSet(s); - if (a != null) { + if (a != null && cancelOnReplace) { a.cancel(); } drain(); @@ -240,7 +243,7 @@ final void drainLoop() { } if (ms != null) { - if (a != null) { + if (a != null && cancelOnReplace) { a.cancel(); } actual = ms; diff --git a/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java index cddd53b8d1..ca19d0d4a3 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java +++ b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java @@ -92,14 +92,6 @@ public static boolean validate(long n) { public static void reportMoreProduced(long n) { RxJavaPlugins.onError(new ProtocolViolationException("More produced than requested: " + n)); } - /** - * Check if the given subscription is the common cancelled subscription. - * @param s the subscription to check - * @return true if the subscription is the common cancelled subscription - */ - public static boolean isCancelled(Subscription s) { - return s == CANCELLED; - } /** * Atomically sets the subscription on the field and cancels the diff --git a/src/main/java/io/reactivex/internal/util/AppendOnlyLinkedArrayList.java b/src/main/java/io/reactivex/internal/util/AppendOnlyLinkedArrayList.java index fce0130657..12c4d062c0 100644 --- a/src/main/java/io/reactivex/internal/util/AppendOnlyLinkedArrayList.java +++ b/src/main/java/io/reactivex/internal/util/AppendOnlyLinkedArrayList.java @@ -125,7 +125,6 @@ public boolean accept(Subscriber subscriber) { return false; } - /** * Interprets the contents as NotificationLite objects and calls * the appropriate Observer method. diff --git a/src/main/java/io/reactivex/internal/util/AtomicThrowable.java b/src/main/java/io/reactivex/internal/util/AtomicThrowable.java index 5f4d4940d8..60c19155c5 100644 --- a/src/main/java/io/reactivex/internal/util/AtomicThrowable.java +++ b/src/main/java/io/reactivex/internal/util/AtomicThrowable.java @@ -23,7 +23,6 @@ */ public final class AtomicThrowable extends AtomicReference { - private static final long serialVersionUID = 3949248817947090603L; /** diff --git a/src/main/java/io/reactivex/internal/util/ExceptionHelper.java b/src/main/java/io/reactivex/internal/util/ExceptionHelper.java index 002f2df215..ecd970c730 100644 --- a/src/main/java/io/reactivex/internal/util/ExceptionHelper.java +++ b/src/main/java/io/reactivex/internal/util/ExceptionHelper.java @@ -14,6 +14,7 @@ package io.reactivex.internal.util; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import io.reactivex.exceptions.CompositeException; @@ -121,6 +122,14 @@ public static Exception throwIfThrowable(Throwable e) thro throw (E)e; } + public static String timeoutMessage(long timeout, TimeUnit unit) { + return "The source did not signal an event for " + + timeout + + " " + + unit.toString().toLowerCase() + + " and has been terminated."; + } + static final class Termination extends Throwable { private static final long serialVersionUID = -4649703670690200604L; diff --git a/src/main/java/io/reactivex/internal/util/HalfSerializer.java b/src/main/java/io/reactivex/internal/util/HalfSerializer.java index 011528ec2a..8e160ab2ea 100644 --- a/src/main/java/io/reactivex/internal/util/HalfSerializer.java +++ b/src/main/java/io/reactivex/internal/util/HalfSerializer.java @@ -74,7 +74,6 @@ public static void onError(Subscriber subscriber, Throwable ex, } } - /** * Emits an onComplete signal or an onError signal with the given error or indicates * the concurrently running onNext should do that. diff --git a/src/main/java/io/reactivex/internal/util/LinkedArrayList.java b/src/main/java/io/reactivex/internal/util/LinkedArrayList.java index a54527bdfa..6dbf3fb6e4 100644 --- a/src/main/java/io/reactivex/internal/util/LinkedArrayList.java +++ b/src/main/java/io/reactivex/internal/util/LinkedArrayList.java @@ -87,6 +87,7 @@ public Object[] head() { public int size() { return size; } + @Override public String toString() { final int cap = capacityHint; diff --git a/src/main/java/io/reactivex/internal/util/OpenHashSet.java b/src/main/java/io/reactivex/internal/util/OpenHashSet.java index 8a1b4fe50e..f971002ad0 100644 --- a/src/main/java/io/reactivex/internal/util/OpenHashSet.java +++ b/src/main/java/io/reactivex/internal/util/OpenHashSet.java @@ -140,7 +140,6 @@ void rehash() { T[] b = (T[])new Object[newCap]; - for (int j = size; j-- != 0; ) { while (a[--i] == null) { } // NOPMD int pos = mix(a[i].hashCode()) & m; diff --git a/src/main/java/io/reactivex/internal/util/Pow2.java b/src/main/java/io/reactivex/internal/util/Pow2.java index d9782a53b0..db4317132c 100644 --- a/src/main/java/io/reactivex/internal/util/Pow2.java +++ b/src/main/java/io/reactivex/internal/util/Pow2.java @@ -11,7 +11,6 @@ * the License for the specific language governing permissions and limitations under the License. */ - /* * Original License: https://github.com/JCTools/JCTools/blob/master/LICENSE * Original location: https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/util/Pow2.java diff --git a/src/main/java/io/reactivex/observables/ConnectableObservable.java b/src/main/java/io/reactivex/observables/ConnectableObservable.java index 1858397e65..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)); } /** @@ -203,7 +220,7 @@ public final Observable refCount(int subscriberCount, long timeout, TimeUnit * during the lifetime of the returned Observable. If this ConnectableObservable * terminates, the connection is never renewed, no matter how Observers come * and go. Use {@link #refCount()} to renew a connection or dispose an active - * connection when all {@code Observers}s have disposed their {@code Disposable}s. + * connection when all {@code Observer}s have disposed their {@code Disposable}s. *

      * This overload does not allow disconnecting the connection established via * {@link #connect(Consumer)}. Use the {@link #autoConnect(int, Consumer)} overload @@ -227,7 +244,7 @@ public Observable autoConnect() { * during the lifetime of the returned Observable. If this ConnectableObservable * terminates, the connection is never renewed, no matter how Observers come * and go. Use {@link #refCount()} to renew a connection or dispose an active - * connection when all {@code Observers}s have disposed their {@code Disposable}s. + * connection when all {@code Observer}s have disposed their {@code Disposable}s. *

      * This overload does not allow disconnecting the connection established via * {@link #connect(Consumer)}. Use the {@link #autoConnect(int, Consumer)} overload @@ -255,7 +272,7 @@ public Observable autoConnect(int numberOfSubscribers) { * during the lifetime of the returned Observable. If this ConnectableObservable * terminates, the connection is never renewed, no matter how Observers come * and go. Use {@link #refCount()} to renew a connection or dispose an active - * connection when all {@code Observers}s have disposed their {@code Disposable}s. + * connection when all {@code Observer}s have disposed their {@code Disposable}s. * * @param numberOfSubscribers the number of subscribers to await before calling connect * on the ConnectableObservable. A non-positive value indicates diff --git a/src/main/java/io/reactivex/observers/BaseTestConsumer.java b/src/main/java/io/reactivex/observers/BaseTestConsumer.java index 373339f020..4c92c27468 100644 --- a/src/main/java/io/reactivex/observers/BaseTestConsumer.java +++ b/src/main/java/io/reactivex/observers/BaseTestConsumer.java @@ -148,7 +148,6 @@ public final int errorCount() { return errors.size(); } - /** * Fail with the given message and add the sequence of errors as suppressed ones. *

      Note this is deliberately the only fail method. Most of the times an assertion @@ -352,11 +351,11 @@ public final U assertError(Predicate errorPredicate) { public final U assertValue(T value) { int s = values.size(); if (s != 1) { - throw fail("Expected: " + valueAndClass(value) + ", Actual: " + values); + throw fail("expected: " + valueAndClass(value) + " but was: " + values); } T v = values.get(0); if (!ObjectHelper.equals(value, v)) { - throw fail("Expected: " + valueAndClass(value) + ", Actual: " + valueAndClass(v)); + throw fail("expected: " + valueAndClass(value) + " but was: " + valueAndClass(v)); } return (U)this; } @@ -451,7 +450,7 @@ public final U assertValueAt(int index, T value) { T v = values.get(index); if (!ObjectHelper.equals(value, v)) { - throw fail("Expected: " + valueAndClass(value) + ", Actual: " + valueAndClass(v)); + throw fail("expected: " + valueAndClass(value) + " but was: " + valueAndClass(v)); } return (U)this; } @@ -513,7 +512,7 @@ public static String valueAndClass(Object o) { public final U assertValueCount(int count) { int s = values.size(); if (s != count) { - throw fail("Value counts differ; Expected: " + count + ", Actual: " + s); + throw fail("Value counts differ; expected: " + count + " but was: " + s); } return (U)this; } @@ -536,14 +535,14 @@ public final U assertNoValues() { public final U assertValues(T... values) { int s = this.values.size(); if (s != values.length) { - throw fail("Value count differs; Expected: " + values.length + " " + Arrays.toString(values) - + ", Actual: " + s + " " + this.values); + throw fail("Value count differs; expected: " + values.length + " " + Arrays.toString(values) + + " but was: " + s + " " + this.values); } for (int i = 0; i < s; i++) { T v = this.values.get(i); T u = values[i]; if (!ObjectHelper.equals(u, v)) { - throw fail("Values at position " + i + " differ; Expected: " + valueAndClass(u) + ", Actual: " + valueAndClass(v)); + throw fail("Values at position " + i + " differ; expected: " + valueAndClass(u) + " but was: " + valueAndClass(v)); } } return (U)this; @@ -556,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) @@ -565,9 +563,14 @@ public final U assertValuesOnly(T... values) { } /** - * Assert that the TestObserver/TestSubscriber received only the specified values in any order. - *

      This helps asserting when the order of the values is not guaranteed, i.e., when merging + * Assert that the TestObserver/TestSubscriber received only items that are in the specified + * collection as well, irrespective of the order they were received. + *

      + * This helps asserting when the order of the values is not guaranteed, i.e., when merging * asynchronous streams. + *

      + * To ensure that only the expected items have been received, no more and no less, in any order, + * apply {@link #assertValueCount(int)} with {@code expected.size()}. * * @param expected the collection of values expected in any order * @return this @@ -624,7 +627,7 @@ public final U assertValueSequence(Iterable sequence) { T v = actualIterator.next(); if (!ObjectHelper.equals(u, v)) { - throw fail("Values at position " + i + " differ; Expected: " + valueAndClass(u) + ", Actual: " + valueAndClass(v)); + throw fail("Values at position " + i + " differ; expected: " + valueAndClass(u) + " but was: " + valueAndClass(v)); } i++; } @@ -734,7 +737,7 @@ public final U assertErrorMessage(String message) { Throwable e = errors.get(0); String errorMessage = e.getMessage(); if (!ObjectHelper.equals(message, errorMessage)) { - throw fail("Error message differs; Expected: " + message + ", Actual: " + errorMessage); + throw fail("Error message differs; exptected: " + message + " but was: " + errorMessage); } } else { throw fail("Multiple errors"); @@ -864,7 +867,6 @@ public final U awaitDone(long time, TimeUnit unit) { return (U)this; } - /** * Assert that the TestObserver/TestSubscriber has received a Disposable but no other events. * @return this @@ -953,7 +955,6 @@ static void sleep(int millis) { } } - /** * Await until the TestObserver/TestSubscriber receives the given * number of items or terminates by sleeping 10 milliseconds at a time @@ -1060,7 +1061,6 @@ public final U assertTimeout() { return (U)this; } - /** * Asserts that some awaitX method has not timed out. *

      History: 2.0.7 - experimental diff --git a/src/main/java/io/reactivex/observers/SafeObserver.java b/src/main/java/io/reactivex/observers/SafeObserver.java index 8dddcf1d9e..77dbfb20da 100644 --- a/src/main/java/io/reactivex/observers/SafeObserver.java +++ b/src/main/java/io/reactivex/observers/SafeObserver.java @@ -63,7 +63,6 @@ public void onSubscribe(@NonNull Disposable d) { } } - @Override public void dispose() { upstream.dispose(); diff --git a/src/main/java/io/reactivex/observers/SerializedObserver.java b/src/main/java/io/reactivex/observers/SerializedObserver.java index d0cdd4eeab..31badf77f8 100644 --- a/src/main/java/io/reactivex/observers/SerializedObserver.java +++ b/src/main/java/io/reactivex/observers/SerializedObserver.java @@ -72,7 +72,6 @@ public void onSubscribe(@NonNull Disposable d) { } } - @Override public void dispose() { upstream.dispose(); @@ -83,7 +82,6 @@ public boolean isDisposed() { return upstream.isDisposed(); } - @Override public void onNext(@NonNull T t) { if (done) { 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/parallel/ParallelFlowable.java b/src/main/java/io/reactivex/parallel/ParallelFlowable.java index 519e731776..13ebb021a4 100644 --- a/src/main/java/io/reactivex/parallel/ParallelFlowable.java +++ b/src/main/java/io/reactivex/parallel/ParallelFlowable.java @@ -228,7 +228,6 @@ public final ParallelFlowable filter(@NonNull Predicate predicate, return RxJavaPlugins.onAssembly(new ParallelFilterTry(this, predicate, errorHandler)); } - /** * Filters the source values on each 'rail' and * handles errors based on the returned value by the handler function. @@ -536,7 +535,6 @@ public final ParallelFlowable doOnNext(@NonNull Consumer onNext) { )); } - /** * Call the specified consumer with the current element passing through any 'rail' and * handles errors based on the given {@link ParallelFailureHandling} enumeration value. diff --git a/src/main/java/io/reactivex/processors/BehaviorProcessor.java b/src/main/java/io/reactivex/processors/BehaviorProcessor.java index ff69ef4273..a81c51085e 100644 --- a/src/main/java/io/reactivex/processors/BehaviorProcessor.java +++ b/src/main/java/io/reactivex/processors/BehaviorProcessor.java @@ -346,7 +346,6 @@ public boolean hasSubscribers() { return subscribers.get().length != 0; } - /* test support*/ int subscriberCount() { return subscribers.get().length; } @@ -447,7 +446,6 @@ public boolean hasValue() { return o != null && !NotificationLite.isComplete(o) && !NotificationLite.isError(o); } - boolean add(BehaviorSubscription rs) { for (;;) { BehaviorSubscription[] a = subscribers.get(); 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/PublishProcessor.java b/src/main/java/io/reactivex/processors/PublishProcessor.java index 713fc4190f..878ae92cbe 100644 --- a/src/main/java/io/reactivex/processors/PublishProcessor.java +++ b/src/main/java/io/reactivex/processors/PublishProcessor.java @@ -141,7 +141,6 @@ public static PublishProcessor create() { subscribers = new AtomicReference[]>(EMPTY); } - @Override protected void subscribeActual(Subscriber t) { PublishSubscription ps = new PublishSubscription(t, this); diff --git a/src/main/java/io/reactivex/processors/ReplayProcessor.java b/src/main/java/io/reactivex/processors/ReplayProcessor.java index 868d465748..b04a17d07e 100644 --- a/src/main/java/io/reactivex/processors/ReplayProcessor.java +++ b/src/main/java/io/reactivex/processors/ReplayProcessor.java @@ -605,6 +605,7 @@ static final class ReplaySubscription extends AtomicInteger implements Subscr this.state = state; this.requested = new AtomicLong(); } + @Override public void request(long n) { if (SubscriptionHelper.validate(n)) { @@ -1048,7 +1049,6 @@ static final class SizeAndTimeBoundReplayBuffer Throwable error; volatile boolean done; - SizeAndTimeBoundReplayBuffer(int maxSize, long maxAge, TimeUnit unit, Scheduler scheduler) { this.maxSize = ObjectHelper.verifyPositive(maxSize, "maxSize"); this.maxAge = ObjectHelper.verifyPositive(maxAge, "maxAge"); @@ -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--; } } @@ -1117,7 +1122,6 @@ void trimFinal() { } } - @Override public void trimHead() { if (head.value != null) { diff --git a/src/main/java/io/reactivex/processors/UnicastProcessor.java b/src/main/java/io/reactivex/processors/UnicastProcessor.java index c754629374..ec4d6c1a77 100644 --- a/src/main/java/io/reactivex/processors/UnicastProcessor.java +++ b/src/main/java/io/reactivex/processors/UnicastProcessor.java @@ -255,7 +255,7 @@ public static UnicastProcessor create(int capacityHint, Runnable onCancel * @since 2.0 */ UnicastProcessor(int capacityHint) { - this(capacityHint,null, true); + this(capacityHint, null, true); } /** @@ -347,7 +347,6 @@ void drainFused(Subscriber a) { for (;;) { if (cancelled) { - q.clear(); downstream.lazySet(null); return; } @@ -506,7 +505,6 @@ protected void subscribeActual(Subscriber s) { final class UnicastQueueSubscription extends BasicIntQueueSubscription { - private static final long serialVersionUID = -4896760517184205454L; @Nullable @@ -551,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 1332a3a019..5af187c121 100644 --- a/src/main/java/io/reactivex/schedulers/Schedulers.java +++ b/src/main/java/io/reactivex/schedulers/Schedulers.java @@ -13,13 +13,13 @@ package io.reactivex.schedulers; +import java.util.concurrent.*; + import io.reactivex.Scheduler; -import io.reactivex.annotations.NonNull; +import io.reactivex.annotations.*; import io.reactivex.internal.schedulers.*; import io.reactivex.plugins.RxJavaPlugins; -import java.util.concurrent.*; - /** * Static factory methods for returning standard Scheduler instances. *

      @@ -29,13 +29,18 @@ *

      * Supported system properties ({@code System.getProperty()}): *

        + *
      • {@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 { @@ -112,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 @@ -128,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 @@ -155,6 +177,7 @@ public static Scheduler computation() { * before the {@link Schedulers} class is referenced in your code. *

      Supported system properties ({@code System.getProperty()}): *

        + *
      • {@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}
      • *
      *

      @@ -297,6 +320,9 @@ 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 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, * cancelling tasks scheduled by this scheduler can be cancelled/interrupted by calling * {@link io.reactivex.disposables.Disposable#dispose()}. In addition, tasks scheduled with @@ -327,7 +353,7 @@ public static Scheduler single() { * } * *

      - * This type of scheduler is less sensitive to leaking {@link io.reactivex.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". *

      @@ -338,7 +364,67 @@ public static Scheduler single() { */ @NonNull public static Scheduler from(@NonNull Executor executor) { - return new ExecutorScheduler(executor); + return new ExecutorScheduler(executor, false); + } + + /** + * 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 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 + * APIs, tasks scheduled with a time delay or periodically will use the + * {@link #single()} scheduler for the timed waiting + * before posting the actual task to the given executor. + *

      + * If the provided executor supports the standard Java {@link ExecutorService} API, + * canceling tasks scheduled by this scheduler can be cancelled/interrupted by calling + * {@link io.reactivex.disposables.Disposable#dispose()}. In addition, tasks scheduled with + * a time delay or periodically will use the {@link #single()} scheduler for the timed waiting + * before posting the actual task to the given executor. + *

      + * If the provided executor supports the standard Java {@link ScheduledExecutorService} API, + * canceling tasks scheduled by this scheduler can be cancelled/interrupted by calling + * {@link io.reactivex.disposables.Disposable#dispose()}. In addition, tasks scheduled with + * a time delay or periodically will use the provided executor. Note, however, if the provided + * {@code ScheduledExecutorService} instance is not single threaded, tasks scheduled + * with a time delay close to each other may end up executing in different order than + * the original schedule() call was issued. This limitation may be lifted in a future patch. + *

      + * Starting, stopping and restarting this scheduler is not supported (no-op) and the provided + * executor's lifecycle must be managed externally: + *

      
      +     * ExecutorService exec = Executors.newSingleThreadedExecutor();
      +     * try {
      +     *     Scheduler scheduler = Schedulers.from(exec, true);
      +     *     Flowable.just(1)
      +     *        .subscribeOn(scheduler)
      +     *        .map(v -> v + 1)
      +     *        .observeOn(scheduler)
      +     *        .blockingSubscribe(System.out::println);
      +     * } finally {
      +     *     exec.shutdown();
      +     * }
      +     * 
      + *

      + * 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 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 + */ + @NonNull + @Experimental + public static Scheduler from(@NonNull Executor executor, boolean interruptibleWorker) { + return new ExecutorScheduler(executor, interruptibleWorker); } /** diff --git a/src/main/java/io/reactivex/schedulers/package-info.java b/src/main/java/io/reactivex/schedulers/package-info.java index 431ca3e8e5..7ba9d63567 100644 --- a/src/main/java/io/reactivex/schedulers/package-info.java +++ b/src/main/java/io/reactivex/schedulers/package-info.java @@ -14,7 +14,9 @@ * limitations under the License. */ /** - * Scheduler implementations, value+time record class and the standard factory class to - * return standard RxJava schedulers or wrap any Executor-based (thread pool) instances. + * Contains notably the factory class of {@link io.reactivex.schedulers.Schedulers Schedulers} providing methods for + * retrieving the standard scheduler instances, the {@link io.reactivex.schedulers.TestScheduler TestScheduler} for testing flows + * with scheduling in a controlled manner and the class {@link io.reactivex.schedulers.Timed Timed} that can hold + * a value and a timestamp associated with it. */ package io.reactivex.schedulers; diff --git a/src/main/java/io/reactivex/subjects/BehaviorSubject.java b/src/main/java/io/reactivex/subjects/BehaviorSubject.java index 7f13dfb432..c58cb40779 100644 --- a/src/main/java/io/reactivex/subjects/BehaviorSubject.java +++ b/src/main/java/io/reactivex/subjects/BehaviorSubject.java @@ -298,7 +298,6 @@ public boolean hasObservers() { return subscribers.get().length != 0; } - /* test support*/ int subscriberCount() { return subscribers.get().length; } diff --git a/src/main/java/io/reactivex/subjects/PublishSubject.java b/src/main/java/io/reactivex/subjects/PublishSubject.java index b97af134ca..f59ab36754 100644 --- a/src/main/java/io/reactivex/subjects/PublishSubject.java +++ b/src/main/java/io/reactivex/subjects/PublishSubject.java @@ -129,7 +129,6 @@ public static PublishSubject create() { subscribers = new AtomicReference[]>(EMPTY); } - @Override protected void subscribeActual(Observer t) { PublishDisposable ps = new PublishDisposable(t, this); diff --git a/src/main/java/io/reactivex/subjects/ReplaySubject.java b/src/main/java/io/reactivex/subjects/ReplaySubject.java index 344ef99fee..622854b5ec 100644 --- a/src/main/java/io/reactivex/subjects/ReplaySubject.java +++ b/src/main/java/io/reactivex/subjects/ReplaySubject.java @@ -700,7 +700,6 @@ public T[] getValues(T[] array) { } } - if (array.length < s) { array = (T[])Array.newInstance(array.getClass().getComponentType(), s); } @@ -1051,7 +1050,6 @@ static final class SizeAndTimeBoundReplayBuffer volatile boolean done; - SizeAndTimeBoundReplayBuffer(int maxSize, long maxAge, TimeUnit unit, Scheduler scheduler) { this.maxSize = ObjectHelper.verifyPositive(maxSize, "maxSize"); this.maxAge = ObjectHelper.verifyPositive(maxAge, "maxAge"); @@ -1073,6 +1071,10 @@ void trim() { TimedNode h = head; for (;;) { + if (size <= 1) { + head = h; + break; + } TimedNode next = h.get(); if (next == null) { head = h; @@ -1085,6 +1087,7 @@ void trim() { } h = next; + size--; } } diff --git a/src/main/java/io/reactivex/subjects/SerializedSubject.java b/src/main/java/io/reactivex/subjects/SerializedSubject.java index 5b8bc16204..ec4263b85e 100644 --- a/src/main/java/io/reactivex/subjects/SerializedSubject.java +++ b/src/main/java/io/reactivex/subjects/SerializedSubject.java @@ -49,7 +49,6 @@ protected void subscribeActual(Observer observer) { actual.subscribe(observer); } - @Override public void onSubscribe(Disposable d) { boolean cancel; diff --git a/src/main/java/io/reactivex/subjects/UnicastSubject.java b/src/main/java/io/reactivex/subjects/UnicastSubject.java index 72f0563637..20aadbd460 100644 --- a/src/main/java/io/reactivex/subjects/UnicastSubject.java +++ b/src/main/java/io/reactivex/subjects/UnicastSubject.java @@ -251,7 +251,6 @@ public static UnicastSubject create(boolean delayError) { return new UnicastSubject(bufferSize(), delayError); } - /** * Creates an UnicastSubject with the given capacity hint and delay error flag. *

      History: 2.0.8 - experimental @@ -421,7 +420,6 @@ void drainFused(Observer a) { if (disposed) { downstream.lazySet(null); - q.clear(); return; } boolean d = done; @@ -522,7 +520,6 @@ public boolean hasComplete() { final class UnicastQueueDisposable extends BasicIntQueueDisposable { - private static final long serialVersionUID = 7926949470189395511L; @Override @@ -560,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/java/io/reactivex/subscribers/DisposableSubscriber.java b/src/main/java/io/reactivex/subscribers/DisposableSubscriber.java index 5c460de70b..076dc9427f 100644 --- a/src/main/java/io/reactivex/subscribers/DisposableSubscriber.java +++ b/src/main/java/io/reactivex/subscribers/DisposableSubscriber.java @@ -74,11 +74,11 @@ * @param the received value type. */ public abstract class DisposableSubscriber implements FlowableSubscriber, Disposable { - final AtomicReference s = new AtomicReference(); + final AtomicReference upstream = new AtomicReference(); @Override public final void onSubscribe(Subscription s) { - if (EndConsumerHelper.setOnce(this.s, s, getClass())) { + if (EndConsumerHelper.setOnce(this.upstream, s, getClass())) { onStart(); } } @@ -87,7 +87,7 @@ public final void onSubscribe(Subscription s) { * Called once the single upstream Subscription is set via onSubscribe. */ protected void onStart() { - s.get().request(Long.MAX_VALUE); + upstream.get().request(Long.MAX_VALUE); } /** @@ -99,7 +99,7 @@ protected void onStart() { * @param n the request amount, positive */ protected final void request(long n) { - s.get().request(n); + upstream.get().request(n); } /** @@ -113,11 +113,11 @@ protected final void cancel() { @Override public final boolean isDisposed() { - return s.get() == SubscriptionHelper.CANCELLED; + return upstream.get() == SubscriptionHelper.CANCELLED; } @Override public final void dispose() { - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); } } diff --git a/src/main/java/io/reactivex/subscribers/ResourceSubscriber.java b/src/main/java/io/reactivex/subscribers/ResourceSubscriber.java index 66c282842c..220fafd191 100644 --- a/src/main/java/io/reactivex/subscribers/ResourceSubscriber.java +++ b/src/main/java/io/reactivex/subscribers/ResourceSubscriber.java @@ -94,7 +94,7 @@ */ public abstract class ResourceSubscriber implements FlowableSubscriber, Disposable { /** The active subscription. */ - private final AtomicReference s = new AtomicReference(); + private final AtomicReference upstream = new AtomicReference(); /** The resource composite, can never be null. */ private final ListCompositeDisposable resources = new ListCompositeDisposable(); @@ -116,7 +116,7 @@ public final void add(Disposable resource) { @Override public final void onSubscribe(Subscription s) { - if (EndConsumerHelper.setOnce(this.s, s, getClass())) { + if (EndConsumerHelper.setOnce(this.upstream, s, getClass())) { long r = missedRequested.getAndSet(0L); if (r != 0L) { s.request(r); @@ -144,7 +144,7 @@ protected void onStart() { * @param n the request amount, must be positive */ protected final void request(long n) { - SubscriptionHelper.deferredRequest(s, missedRequested, n); + SubscriptionHelper.deferredRequest(upstream, missedRequested, n); } /** @@ -156,7 +156,7 @@ protected final void request(long n) { */ @Override public final void dispose() { - if (SubscriptionHelper.cancel(s)) { + if (SubscriptionHelper.cancel(upstream)) { resources.dispose(); } } @@ -167,6 +167,6 @@ public final void dispose() { */ @Override public final boolean isDisposed() { - return SubscriptionHelper.isCancelled(s.get()); + return upstream.get() == SubscriptionHelper.CANCELLED; } } diff --git a/src/main/java/io/reactivex/subscribers/SafeSubscriber.java b/src/main/java/io/reactivex/subscribers/SafeSubscriber.java index a7c1fb3ad6..e903a5519c 100644 --- a/src/main/java/io/reactivex/subscribers/SafeSubscriber.java +++ b/src/main/java/io/reactivex/subscribers/SafeSubscriber.java @@ -176,7 +176,6 @@ public void onComplete() { return; } - try { downstream.onComplete(); } catch (Throwable e) { diff --git a/src/main/java/io/reactivex/subscribers/TestSubscriber.java b/src/main/java/io/reactivex/subscribers/TestSubscriber.java index 30c1a22b03..3b02dbdd09 100644 --- a/src/main/java/io/reactivex/subscribers/TestSubscriber.java +++ b/src/main/java/io/reactivex/subscribers/TestSubscriber.java @@ -167,7 +167,6 @@ public void onSubscribe(Subscription s) { } } - downstream.onSubscribe(s); long mr = missedRequested.getAndSet(0L); 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 32461d5d41..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.*; @@ -2029,6 +2028,7 @@ public void onSubscribe(Disposable d) { }; } } + @Test(timeout = 5000, expected = TestException.class) public void liftOnCompleteError() { Completable c = normal.completable.lift(new CompletableOperatorSwap()); @@ -2397,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) @@ -4323,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()); } @@ -4660,7 +4662,6 @@ public void accept(final Throwable throwable) throws Exception { } } - @Test(timeout = 5000) public void subscribeTwoCallbacksDispose() { PublishProcessor pp = PublishProcessor.create(); diff --git a/src/test/java/io/reactivex/disposables/CompositeDisposableTest.java b/src/test/java/io/reactivex/disposables/CompositeDisposableTest.java index ce8cd412b2..aec399a3e7 100644 --- a/src/test/java/io/reactivex/disposables/CompositeDisposableTest.java +++ b/src/test/java/io/reactivex/disposables/CompositeDisposableTest.java @@ -277,6 +277,7 @@ public void run() { // we should have only disposed once assertEquals(1, counter.get()); } + @Test public void testTryRemoveIfNotIn() { CompositeDisposable cd = new CompositeDisposable(); 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/CompositeExceptionTest.java b/src/test/java/io/reactivex/exceptions/CompositeExceptionTest.java index 4b9f96ba8a..2737b90712 100644 --- a/src/test/java/io/reactivex/exceptions/CompositeExceptionTest.java +++ b/src/test/java/io/reactivex/exceptions/CompositeExceptionTest.java @@ -182,6 +182,7 @@ public void testNullCollection() { composite.getCause(); composite.printStackTrace(); } + @Test public void testNullElement() { CompositeException composite = new CompositeException(Collections.singletonList((Throwable) null)); @@ -363,7 +364,22 @@ public synchronized Throwable getCause() { } }; CompositeException ex = new CompositeException(throwable); - assertSame(ex, ex.getRootCause(ex)); + assertSame(ex0, ex.getRootCause(ex)); + } + + @Test + public void rootCauseSelf() { + Throwable throwable = new Throwable() { + + private static final long serialVersionUID = -4398003222998914415L; + + @Override + public synchronized Throwable getCause() { + return this; + } + }; + CompositeException tmp = new CompositeException(new TestException()); + assertSame(throwable, tmp.getRootCause(throwable)); } } diff --git a/src/test/java/io/reactivex/exceptions/ExceptionsTest.java b/src/test/java/io/reactivex/exceptions/ExceptionsTest.java index 43b316a7f7..e1cff53e29 100644 --- a/src/test/java/io/reactivex/exceptions/ExceptionsTest.java +++ b/src/test/java/io/reactivex/exceptions/ExceptionsTest.java @@ -53,11 +53,13 @@ public void accept(Integer t1) { }); - TestHelper.assertError(errors, 0, RuntimeException.class, "hello"); + TestHelper.assertError(errors, 0, RuntimeException.class); + assertTrue(errors.get(0).toString(), errors.get(0).getMessage().contains("hello")); RxJavaPlugins.reset(); } /** + * Outdated test: Observer should not suppress errors from onCompleted. * https://github.com/ReactiveX/RxJava/issues/3885 */ @Ignore("v2 components should not throw") @@ -199,6 +201,7 @@ public void onNext(Integer t) { } /** + * Outdated test: throwing from onError handler. * https://github.com/ReactiveX/RxJava/issues/969 */ @Ignore("v2 components should not throw") @@ -236,6 +239,7 @@ public void onNext(Object o) { } /** + * Outdated test: throwing from onError. * https://github.com/ReactiveX/RxJava/issues/2998 * @throws Exception on arbitrary errors */ @@ -275,6 +279,7 @@ public void onNext(GroupedObservable integerIntegerGroupedObse } /** + * Outdated test: throwing from onError. * https://github.com/ReactiveX/RxJava/issues/2998 * @throws Exception on arbitrary errors */ diff --git a/src/test/java/io/reactivex/exceptions/OnErrorNotImplementedExceptionTest.java b/src/test/java/io/reactivex/exceptions/OnErrorNotImplementedExceptionTest.java index d7b69ca107..4097f9a52e 100644 --- a/src/test/java/io/reactivex/exceptions/OnErrorNotImplementedExceptionTest.java +++ b/src/test/java/io/reactivex/exceptions/OnErrorNotImplementedExceptionTest.java @@ -108,7 +108,6 @@ public void singleSubscribe1() { .subscribe(Functions.emptyConsumer()); } - @Test public void maybeSubscribe0() { Maybe.error(new TestException()) 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/exceptions/TestException.java b/src/test/java/io/reactivex/exceptions/TestException.java index 6893fe54b4..eef7a47a85 100644 --- a/src/test/java/io/reactivex/exceptions/TestException.java +++ b/src/test/java/io/reactivex/exceptions/TestException.java @@ -52,6 +52,4 @@ public TestException(String message) { public TestException(Throwable cause) { super(cause); } - - } diff --git a/src/test/java/io/reactivex/flowable/FlowableBackpressureTests.java b/src/test/java/io/reactivex/flowable/FlowableBackpressureTests.java index 9db71ac805..7898628f70 100644 --- a/src/test/java/io/reactivex/flowable/FlowableBackpressureTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableBackpressureTests.java @@ -181,7 +181,6 @@ public void testMergeAsyncThenObserveOnLoop() { .take(num) .subscribe(ts); - ts.awaitTerminalEvent(5, TimeUnit.SECONDS); ts.assertComplete(); ts.assertNoErrors(); diff --git a/src/test/java/io/reactivex/flowable/FlowableCollectTest.java b/src/test/java/io/reactivex/flowable/FlowableCollectTest.java index 09bb562d2b..f187d5b240 100644 --- a/src/test/java/io/reactivex/flowable/FlowableCollectTest.java +++ b/src/test/java/io/reactivex/flowable/FlowableCollectTest.java @@ -83,7 +83,6 @@ public void accept(StringBuilder sb, Integer v) { assertEquals("1-2-3", value); } - @Test public void testFactoryFailureResultsInErrorEmissionFlowable() { final RuntimeException e = new RuntimeException(); @@ -167,7 +166,6 @@ public void accept(Object o, Integer t) { assertFalse(added.get()); } - @SuppressWarnings("unchecked") @Test public void collectIntoFlowable() { @@ -183,7 +181,6 @@ public void accept(HashSet s, Integer v) throws Exception { .assertResult(new HashSet(Arrays.asList(1, 2))); } - @Test public void testCollectToList() { Single> o = Flowable.just(1, 2, 3) @@ -238,7 +235,6 @@ public void accept(StringBuilder sb, Integer v) { assertEquals("1-2-3", value); } - @Test public void testFactoryFailureResultsInErrorEmission() { final RuntimeException e = new RuntimeException(); @@ -319,7 +315,6 @@ public void accept(Object o, Integer t) { assertFalse(added.get()); } - @SuppressWarnings("unchecked") @Test public void collectInto() { diff --git a/src/test/java/io/reactivex/flowable/FlowableConversionTest.java b/src/test/java/io/reactivex/flowable/FlowableConversionTest.java index a7d908111a..36bcb643f6 100644 --- a/src/test/java/io/reactivex/flowable/FlowableConversionTest.java +++ b/src/test/java/io/reactivex/flowable/FlowableConversionTest.java @@ -206,7 +206,7 @@ public String apply(String a, String n) { public void testConvertToConcurrentQueue() { final AtomicReference thrown = new AtomicReference(null); final AtomicBoolean isFinished = new AtomicBoolean(false); - ConcurrentLinkedQueue queue = Flowable.range(0,5) + ConcurrentLinkedQueue queue = Flowable.range(0, 5) .flatMap(new Function>() { @Override public Publisher apply(final Integer i) { diff --git a/src/test/java/io/reactivex/flowable/FlowableErrorHandlingTests.java b/src/test/java/io/reactivex/flowable/FlowableErrorHandlingTests.java index e86aa39266..21f05aa8d4 100644 --- a/src/test/java/io/reactivex/flowable/FlowableErrorHandlingTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableErrorHandlingTests.java @@ -28,7 +28,8 @@ public class FlowableErrorHandlingTests { /** - * Test that an error from a user provided Observer.onNext is handled and emitted to the onError + * Test that an error from a user provided Observer.onNext + * is handled and emitted to the onError. * @throws InterruptedException if the test is interrupted */ @Test @@ -63,7 +64,8 @@ public void onNext(Long args) { } /** - * Test that an error from a user provided Observer.onNext is handled and emitted to the onError + * Test that an error from a user provided Observer.onNext + * is handled and emitted to the onError. * even when done across thread boundaries with observeOn * @throws InterruptedException if the test is interrupted */ 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/FlowableNullTests.java b/src/test/java/io/reactivex/flowable/FlowableNullTests.java index 80f70a75ad..a9acc4f061 100644 --- a/src/test/java/io/reactivex/flowable/FlowableNullTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableNullTests.java @@ -466,7 +466,7 @@ public void intervalPeriodSchedulerNull() { @Test(expected = NullPointerException.class) public void intervalRangeUnitNull() { - Flowable.intervalRange(1,1, 1, 1, null); + Flowable.intervalRange(1, 1, 1, 1, null); } @Test(expected = NullPointerException.class) @@ -2724,7 +2724,6 @@ public Object apply(Integer a, Integer b) { }); } - @Test(expected = NullPointerException.class) public void zipWithCombinerNull() { just1.zipWith(just1, null); diff --git a/src/test/java/io/reactivex/flowable/FlowableReduceTests.java b/src/test/java/io/reactivex/flowable/FlowableReduceTests.java index e98c56f0f5..8381a510a4 100644 --- a/src/test/java/io/reactivex/flowable/FlowableReduceTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableReduceTests.java @@ -77,7 +77,6 @@ public Movie apply(Movie t1, Movie t2) { assertNotNull(reduceResult2); } - @Test public void reduceInts() { Flowable f = Flowable.just(1, 2, 3); diff --git a/src/test/java/io/reactivex/flowable/FlowableSubscriberTest.java b/src/test/java/io/reactivex/flowable/FlowableSubscriberTest.java index b72df7a98c..d0a0e1a88e 100644 --- a/src/test/java/io/reactivex/flowable/FlowableSubscriberTest.java +++ b/src/test/java/io/reactivex/flowable/FlowableSubscriberTest.java @@ -231,9 +231,8 @@ public void onError(Throwable e) { public void onNext(String t) { } - - }; + return as; } }; @@ -468,7 +467,7 @@ public void onNext(Integer t) { public void testNegativeRequestThrowsIllegalArgumentException() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference exception = new AtomicReference(); - Flowable.just(1,2,3,4).subscribe(new DefaultSubscriber() { + Flowable.just(1, 2, 3, 4).subscribe(new DefaultSubscriber() { @Override public void onStart() { @@ -499,7 +498,8 @@ public void onNext(Integer t) { @Test public void testOnStartRequestsAreAdditive() { final List list = new ArrayList(); - Flowable.just(1,2,3,4,5).subscribe(new DefaultSubscriber() { + Flowable.just(1, 2, 3, 4, 5) + .subscribe(new DefaultSubscriber() { @Override public void onStart() { request(3); @@ -520,13 +520,13 @@ public void onError(Throwable e) { public void onNext(Integer t) { list.add(t); }}); - assertEquals(Arrays.asList(1,2,3,4,5), list); + assertEquals(Arrays.asList(1, 2, 3, 4, 5), list); } @Test public void testOnStartRequestsAreAdditiveAndOverflowBecomesMaxValue() { final List list = new ArrayList(); - Flowable.just(1,2,3,4,5).subscribe(new DefaultSubscriber() { + Flowable.just(1, 2, 3, 4, 5).subscribe(new DefaultSubscriber() { @Override public void onStart() { request(2); @@ -547,7 +547,7 @@ public void onError(Throwable e) { public void onNext(Integer t) { list.add(t); }}); - assertEquals(Arrays.asList(1,2,3,4,5), list); + assertEquals(Arrays.asList(1, 2, 3, 4, 5), list); } @Test @@ -771,7 +771,6 @@ public void safeSubscriberAlreadySafe() { ts.assertResult(1); } - @Test public void methodTestNoCancel() { PublishProcessor pp = PublishProcessor.create(); @@ -816,7 +815,7 @@ public Subscriber apply(Flowable a, Subscriber b) throws Exception { Flowable.just(1).test(); fail("Should have thrown"); } catch (NullPointerException ex) { - assertEquals("Plugin returned null Subscriber", ex.getMessage()); + assertEquals("The RxJavaPlugins.onSubscribe hook returned a null FlowableSubscriber. Please check the handler provided to RxJavaPlugins.setOnFlowableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins", ex.getMessage()); } } finally { RxJavaPlugins.reset(); diff --git a/src/test/java/io/reactivex/flowable/FlowableTests.java b/src/test/java/io/reactivex/flowable/FlowableTests.java index f3b7d2b371..c9e226da9e 100644 --- a/src/test/java/io/reactivex/flowable/FlowableTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableTests.java @@ -20,16 +20,17 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import io.reactivex.Observable; import org.junit.*; import org.mockito.InOrder; import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.Observable; 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.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.*; @@ -148,7 +149,6 @@ public Throwable call() { verify(w, times(1)).onError(any(RuntimeException.class)); } - @Test public void testCountAFewItems() { Flowable flowable = Flowable.just("a", "b", "c", "d"); @@ -342,7 +342,8 @@ public void testOnSubscribeFails() { @Test public void testMaterializeDematerializeChaining() { Flowable obs = Flowable.just(1); - Flowable chained = obs.materialize().dematerialize(); + Flowable chained = obs.materialize() + .dematerialize(Functions.>identity()); Subscriber subscriber = TestHelper.mockSubscriber(); @@ -862,7 +863,6 @@ public void testContainsWithEmptyObservableFlowable() { verify(subscriber, times(1)).onComplete(); } - @Test public void testContains() { Single single = Flowable.just("a", "b", "c").contains("b"); // FIXME nulls not allowed, changed to "c" @@ -1028,7 +1028,7 @@ public void testAmbWith() { public void testTakeWhileToList() { final int expectedCount = 3; final AtomicInteger count = new AtomicInteger(); - for (int i = 0;i < expectedCount; i++) { + for (int i = 0; i < expectedCount; i++) { Flowable .just(Boolean.TRUE, Boolean.FALSE) .takeWhile(new Predicate() { @@ -1078,7 +1078,7 @@ public void testErrorThrownIssue1685() { Flowable.error(new RuntimeException("oops")) .materialize() .delay(1, TimeUnit.SECONDS) - .dematerialize() + .dematerialize(Functions.>identity()) .subscribe(processor); processor.subscribe(); 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/flowable/FlowableZipTests.java b/src/test/java/io/reactivex/flowable/FlowableZipTests.java index 78ffa8cee5..d901d36bc2 100644 --- a/src/test/java/io/reactivex/flowable/FlowableZipTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableZipTests.java @@ -129,7 +129,6 @@ public void accept(ExtendedResult t1) { } }; - @Test public void zipWithDelayError() { Flowable.just(1) diff --git a/src/test/java/io/reactivex/internal/SubscribeWithTest.java b/src/test/java/io/reactivex/internal/SubscribeWithTest.java index e2c23b51d5..942762009c 100644 --- a/src/test/java/io/reactivex/internal/SubscribeWithTest.java +++ b/src/test/java/io/reactivex/internal/SubscribeWithTest.java @@ -30,7 +30,6 @@ public void withFlowable() { .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); } - @Test public void withObservable() { Observable.range(1, 10) @@ -38,7 +37,6 @@ public void withObservable() { .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); } - class ObserverImpl implements SingleObserver, CompletableObserver { Object value; diff --git a/src/test/java/io/reactivex/internal/observers/BasicFuseableObserverTest.java b/src/test/java/io/reactivex/internal/observers/BasicFuseableObserverTest.java index 93aef221b2..797ae8e07a 100644 --- a/src/test/java/io/reactivex/internal/observers/BasicFuseableObserverTest.java +++ b/src/test/java/io/reactivex/internal/observers/BasicFuseableObserverTest.java @@ -30,13 +30,16 @@ public void offer() { public Integer poll() throws Exception { return null; } + @Override public int requestFusion(int mode) { return 0; } + @Override public void onNext(Integer value) { } + @Override protected boolean beforeDownstream() { return false; @@ -58,10 +61,12 @@ public void offer2() { public Integer poll() throws Exception { return null; } + @Override public int requestFusion(int mode) { return 0; } + @Override public void onNext(Integer value) { } diff --git a/src/test/java/io/reactivex/internal/observers/BlockingMultiObserverTest.java b/src/test/java/io/reactivex/internal/observers/BlockingMultiObserverTest.java index 6b4238f8f6..793253504b 100644 --- a/src/test/java/io/reactivex/internal/observers/BlockingMultiObserverTest.java +++ b/src/test/java/io/reactivex/internal/observers/BlockingMultiObserverTest.java @@ -13,9 +13,11 @@ package io.reactivex.internal.observers; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.junit.Test; @@ -132,4 +134,17 @@ public void run() { assertTrue(bmo.blockingGetError(1, TimeUnit.MINUTES) instanceof TestException); } + + @Test + public void blockingGetErrorTimedOut() { + final BlockingMultiObserver bmo = new BlockingMultiObserver(); + + try { + assertNull(bmo.blockingGetError(1, TimeUnit.NANOSECONDS)); + fail("Should have thrown"); + } catch (RuntimeException expected) { + assertEquals(TimeoutException.class, expected.getCause().getClass()); + assertEquals(timeoutMessage(1, TimeUnit.NANOSECONDS), expected.getCause().getMessage()); + } + } } diff --git a/src/test/java/io/reactivex/internal/observers/DeferredScalarObserverTest.java b/src/test/java/io/reactivex/internal/observers/DeferredScalarObserverTest.java index 8defb098ef..7d2640b71c 100644 --- a/src/test/java/io/reactivex/internal/observers/DeferredScalarObserverTest.java +++ b/src/test/java/io/reactivex/internal/observers/DeferredScalarObserverTest.java @@ -181,7 +181,6 @@ static final class TakeLast extends DeferredScalarObserver { super(downstream); } - @Override public void onNext(Integer value) { this.value = value; diff --git a/src/test/java/io/reactivex/internal/observers/FutureObserverTest.java b/src/test/java/io/reactivex/internal/observers/FutureObserverTest.java index bf91a78a4e..7ee71945bc 100644 --- a/src/test/java/io/reactivex/internal/observers/FutureObserverTest.java +++ b/src/test/java/io/reactivex/internal/observers/FutureObserverTest.java @@ -13,6 +13,7 @@ package io.reactivex.internal.observers; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; import java.util.*; @@ -352,4 +353,14 @@ public void run() { assertEquals(1, fo.get().intValue()); } + + @Test + public void getTimedOut() throws Exception { + try { + fo.get(1, TimeUnit.NANOSECONDS); + fail("Should have thrown"); + } catch (TimeoutException expected) { + assertEquals(timeoutMessage(1, TimeUnit.NANOSECONDS), expected.getMessage()); + } + } } diff --git a/src/test/java/io/reactivex/internal/observers/FutureSingleObserverTest.java b/src/test/java/io/reactivex/internal/observers/FutureSingleObserverTest.java index 1dc0451434..9cafad4569 100644 --- a/src/test/java/io/reactivex/internal/observers/FutureSingleObserverTest.java +++ b/src/test/java/io/reactivex/internal/observers/FutureSingleObserverTest.java @@ -13,6 +13,7 @@ package io.reactivex.internal.observers; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; import java.util.concurrent.*; @@ -89,8 +90,8 @@ public void timeout() throws Exception { try { f.get(100, TimeUnit.MILLISECONDS); fail("Should have thrown"); - } catch (TimeoutException ex) { - // expected + } catch (TimeoutException expected) { + assertEquals(timeoutMessage(100, TimeUnit.MILLISECONDS), expected.getMessage()); } } diff --git a/src/test/java/io/reactivex/internal/observers/LambdaObserverTest.java b/src/test/java/io/reactivex/internal/observers/LambdaObserverTest.java index 8e520fd402..81221cfc21 100644 --- a/src/test/java/io/reactivex/internal/observers/LambdaObserverTest.java +++ b/src/test/java/io/reactivex/internal/observers/LambdaObserverTest.java @@ -247,6 +247,7 @@ public void accept(Disposable d) throws Exception { RxJavaPlugins.reset(); } } + @Test public void badSourceEmitAfterDone() { List errors = TestHelper.trackPluginErrors(); diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableAmbTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableAmbTest.java index f6f3dc0337..f4a8a084b8 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableAmbTest.java @@ -16,6 +16,7 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; @@ -23,10 +24,13 @@ import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.operators.completable.CompletableAmb.Amb; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.*; public class CompletableAmbTest { @@ -173,6 +177,7 @@ public void ambRace() { CompositeDisposable cd = new CompositeDisposable(); AtomicBoolean once = new AtomicBoolean(); Amb a = new Amb(once, cd, to); + a.onSubscribe(Disposables.empty()); a.onComplete(); a.onComplete(); @@ -187,7 +192,6 @@ public void ambRace() { } } - @Test public void untilCompletableMainComplete() { CompletableSubject main = CompletableSubject.create(); @@ -260,4 +264,54 @@ public void untilCompletableOtherError() { to.assertFailure(TestException.class); } + @Test + public void noWinnerErrorDispose() throws Exception { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Completable.ambArray( + Completable.error(ex) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Completable.never() + ) + .subscribe(Functions.EMPTY_ACTION, new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @Test + public void noWinnerCompleteDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Completable.ambArray( + Completable.complete() + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Completable.never() + ) + .subscribe(new Action() { + @Override + public void run() throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletableabTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletableabTest.java new file mode 100644 index 0000000000..34b9c82436 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletableabTest.java @@ -0,0 +1,185 @@ +/** + * 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.completable; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.Schedulers; + +import static org.junit.Assert.*; + +public class CompletableAndThenCompletableabTest { + @Test(expected = NullPointerException.class) + public void andThenCompletableCompleteNull() { + Completable.complete() + .andThen((Completable) null); + } + + @Test + public void andThenCompletableCompleteComplete() { + Completable.complete() + .andThen(Completable.complete()) + .test() + .assertComplete(); + } + + @Test + public void andThenCompletableCompleteError() { + Completable.complete() + .andThen(Completable.error(new TestException("test"))) + .test() + .assertNotComplete() + .assertNoValues() + .assertError(TestException.class) + .assertErrorMessage("test"); + } + + @Test + public void andThenCompletableCompleteNever() { + Completable.complete() + .andThen(Completable.never()) + .test() + .assertNoValues() + .assertNoErrors() + .assertNotComplete(); + } + + @Test + public void andThenCompletableErrorComplete() { + Completable.error(new TestException("bla")) + .andThen(Completable.complete()) + .test() + .assertNotComplete() + .assertNoValues() + .assertError(TestException.class) + .assertErrorMessage("bla"); + } + + @Test + public void andThenCompletableErrorNever() { + Completable.error(new TestException("bla")) + .andThen(Completable.never()) + .test() + .assertNotComplete() + .assertNoValues() + .assertError(TestException.class) + .assertErrorMessage("bla"); + } + + @Test + public void andThenCompletableErrorError() { + Completable.error(new TestException("error1")) + .andThen(Completable.error(new TestException("error2"))) + .test() + .assertNotComplete() + .assertNoValues() + .assertError(TestException.class) + .assertErrorMessage("error1"); + } + + @Test + public void andThenCanceled() { + final AtomicInteger completableRunCount = new AtomicInteger(); + Completable.fromRunnable(new Runnable() { + @Override + public void run() { + completableRunCount.incrementAndGet(); + } + }) + .andThen(Completable.complete()) + .test(true) + .assertEmpty(); + assertEquals(1, completableRunCount.get()); + } + + @Test + public void andThenFirstCancels() { + final TestObserver to = new TestObserver(); + Completable.fromRunnable(new Runnable() { + @Override + public void run() { + to.cancel(); + } + }) + .andThen(Completable.complete()) + .subscribe(to); + to + .assertNotComplete() + .assertNoErrors(); + } + + @Test + public void andThenSecondCancels() { + final TestObserver to = new TestObserver(); + Completable.complete() + .andThen(Completable.fromRunnable(new Runnable() { + @Override + public void run() { + to.cancel(); + } + })) + .subscribe(to); + to + .assertNotComplete() + .assertNoErrors(); + } + + @Test + public void andThenDisposed() { + TestHelper.checkDisposed(Completable.complete() + .andThen(Completable.complete())); + } + + @Test + public void andThenNoInterrupt() throws InterruptedException { + for (int k = 0; k < 100; k++) { + final int count = 10; + final CountDownLatch latch = new CountDownLatch(count); + final boolean[] interrupted = {false}; + + for (int i = 0; i < count; i++) { + Completable.complete() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .andThen(Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + try { + Thread.sleep(30); + } catch (InterruptedException e) { + System.out.println("Interrupted! " + Thread.currentThread()); + interrupted[0] = true; + } + } + })) + .subscribe(new Action() { + @Override + public void run() throws Exception { + latch.countDown(); + } + }); + } + + latch.await(); + assertFalse("The second Completable was interrupted!", interrupted[0]); + } + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenTest.java index a5d9b3a279..c873d5472d 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenTest.java @@ -13,15 +13,9 @@ package io.reactivex.internal.operators.completable; -import io.reactivex.Completable; -import io.reactivex.Maybe; -import io.reactivex.functions.Action; -import io.reactivex.schedulers.Schedulers; - -import java.util.concurrent.CountDownLatch; - import org.junit.Test; -import static org.junit.Assert.*; + +import io.reactivex.*; public class CompletableAndThenTest { @Test(expected = NullPointerException.class) @@ -69,39 +63,4 @@ public void andThenMaybeError() { .assertError(RuntimeException.class) .assertErrorMessage("bla"); } - - @Test - public void andThenNoInterrupt() throws InterruptedException { - for (int k = 0; k < 100; k++) { - final int count = 10; - final CountDownLatch latch = new CountDownLatch(count); - final boolean[] interrupted = { false }; - - for (int i = 0; i < count; i++) { - Completable.complete() - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.io()) - .andThen(Completable.fromAction(new Action() { - @Override - public void run() throws Exception { - try { - Thread.sleep(30); - } catch (InterruptedException e) { - System.out.println("Interrupted! " + Thread.currentThread()); - interrupted[0] = true; - } - } - })) - .subscribe(new Action() { - @Override - public void run() throws Exception { - latch.countDown(); - } - }); - } - - latch.await(); - assertFalse("The second Completable was interrupted!", interrupted[0]); - } - } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableDelaySubscriptionTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableDelaySubscriptionTest.java new file mode 100644 index 0000000000..7148233f9d --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableDelaySubscriptionTest.java @@ -0,0 +1,169 @@ +/** + * 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.completable; + +import static org.junit.Assert.*; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import io.reactivex.Completable; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.TestScheduler; +import io.reactivex.subjects.CompletableSubject; + +public class CompletableDelaySubscriptionTest { + + @Test + public void normal() { + final AtomicInteger counter = new AtomicInteger(); + + Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + counter.incrementAndGet(); + } + }) + .delaySubscription(100, TimeUnit.MILLISECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(); + + assertEquals(1, counter.get()); + } + + @Test + public void error() { + final AtomicInteger counter = new AtomicInteger(); + + Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + counter.incrementAndGet(); + + throw new TestException(); + } + }) + .delaySubscription(100, TimeUnit.MILLISECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(TestException.class); + + assertEquals(1, counter.get()); + } + + @Test + public void disposeBeforeTime() { + TestScheduler scheduler = new TestScheduler(); + + final AtomicInteger counter = new AtomicInteger(); + + Completable result = Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + counter.incrementAndGet(); + } + }) + .delaySubscription(100, TimeUnit.MILLISECONDS, scheduler); + TestObserver to = result.test(); + + to.assertEmpty(); + + scheduler.advanceTimeBy(90, TimeUnit.MILLISECONDS); + + to.dispose(); + + scheduler.advanceTimeBy(15, TimeUnit.MILLISECONDS); + + to.assertEmpty(); + + assertEquals(0, counter.get()); + } + + @Test + public void timestep() { + TestScheduler scheduler = new TestScheduler(); + final AtomicInteger counter = new AtomicInteger(); + + Completable result = Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + counter.incrementAndGet(); + } + }) + .delaySubscription(100, TimeUnit.MILLISECONDS, scheduler); + + TestObserver to = result.test(); + + scheduler.advanceTimeBy(90, TimeUnit.MILLISECONDS); + to.assertEmpty(); + scheduler.advanceTimeBy(15, TimeUnit.MILLISECONDS); + to.assertResult(); + + assertEquals(1, counter.get()); + } + + @Test + public void timestepError() { + TestScheduler scheduler = new TestScheduler(); + final AtomicInteger counter = new AtomicInteger(); + + Completable result = Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + counter.incrementAndGet(); + + throw new TestException(); + } + }) + .delaySubscription(100, TimeUnit.MILLISECONDS, scheduler); + + TestObserver to = result.test(); + + scheduler.advanceTimeBy(90, TimeUnit.MILLISECONDS); + + to.assertEmpty(); + + scheduler.advanceTimeBy(15, TimeUnit.MILLISECONDS); + + to.assertFailure(TestException.class); + + assertEquals(1, counter.get()); + } + + @Test + public void disposeMain() { + CompletableSubject cs = CompletableSubject.create(); + + TestScheduler scheduler = new TestScheduler(); + + TestObserver to = cs + .delaySubscription(1, TimeUnit.SECONDS, scheduler) + .test(); + + assertFalse(cs.hasObservers()); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + assertTrue(cs.hasObservers()); + + to.dispose(); + + assertFalse(cs.hasObservers()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableDelayTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableDelayTest.java index 174a520e0f..be72ce0ad4 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableDelayTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableDelayTest.java @@ -20,11 +20,14 @@ import org.junit.Test; -import io.reactivex.*; +import io.reactivex.CompletableSource; +import io.reactivex.TestHelper; +import io.reactivex.Completable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.observers.TestObserver; -import io.reactivex.schedulers.*; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.schedulers.TestScheduler; public class CompletableDelayTest { 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/completable/CompletableMaterializeTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableMaterializeTest.java new file mode 100644 index 0000000000..aec11e5a61 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableMaterializeTest.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.operators.completable; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.subjects.CompletableSubject; + +public class CompletableMaterializeTest { + + @Test + @SuppressWarnings("unchecked") + public void error() { + TestException ex = new TestException(); + Completable.error(ex) + .materialize() + .test() + .assertResult(Notification.createOnError(ex)); + } + + @Test + @SuppressWarnings("unchecked") + public void empty() { + Completable.complete() + .materialize() + .test() + .assertResult(Notification.createOnComplete()); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeCompletableToSingle(new Function>>() { + @Override + public SingleSource> apply(Completable v) throws Exception { + return v.materialize(); + } + }); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(CompletableSubject.create().materialize()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableResumeNextTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableResumeNextTest.java index c2a3af2769..1633954fbf 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableResumeNextTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableResumeNextTest.java @@ -40,7 +40,6 @@ public CompletableSource apply(Completable c) throws Exception { }); } - @Test public void disposeInResume() { TestHelper.checkDisposedCompletable(new Function() { diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableSubscribeTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableSubscribeTest.java index 543d7015c4..4f546e548f 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableSubscribeTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableSubscribeTest.java @@ -30,7 +30,6 @@ public void subscribeAlreadyCancelled() { assertFalse(pp.hasSubscribers()); } - @Test public void methodTestNoCancel() { PublishSubject ps = PublishSubject.create(); diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableTimeoutTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableTimeoutTest.java index 3df78394f3..a0de2b25aa 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableTimeoutTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableTimeoutTest.java @@ -13,6 +13,7 @@ package io.reactivex.internal.operators.completable; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; import java.util.List; @@ -40,7 +41,7 @@ public void timeoutException() throws Exception { .timeout(100, TimeUnit.MILLISECONDS, Schedulers.io()) .test() .awaitDone(5, TimeUnit.SECONDS) - .assertFailure(TimeoutException.class); + .assertFailureAndMessage(TimeoutException.class, timeoutMessage(100, TimeUnit.MILLISECONDS)); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableUsingTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableUsingTest.java index 598abdd29c..5d1a4cfc42 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableUsingTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableUsingTest.java @@ -347,7 +347,6 @@ public void accept(Object d) throws Exception { .assertFailure(TestException.class); } - @Test public void emptyDisposerCrashes() { Completable.using(new Callable() { 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 71c075c2c4..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()); } } @@ -103,7 +103,6 @@ public void constructorshouldbeprivate() { TestHelper.checkUtilityClass(BlockingFlowableMostRecent.class); } - @Test public void empty() { Iterator it = Flowable.empty() 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 7ab08915d2..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; @@ -124,6 +123,7 @@ public boolean test(Integer i) { assertFalse(allOdd.blockingGet()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstream() { Flowable source = Flowable.just(1) @@ -297,6 +297,7 @@ public boolean test(Integer i) { assertFalse(allOdd.blockingFirst()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstreamFlowable() { Flowable source = Flowable.just(1) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java index 9e68ec3948..5b5941fbf4 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java @@ -19,8 +19,8 @@ import java.io.IOException; import java.lang.reflect.Method; import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; import org.junit.*; import org.mockito.InOrder; @@ -30,6 +30,7 @@ import io.reactivex.disposables.CompositeDisposable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.util.CrashingMappedIterable; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; @@ -236,7 +237,6 @@ public void testBackpressure() { assertEquals(Flowable.bufferSize() * 2, ts.values().size()); } - @SuppressWarnings("unchecked") @Test public void testSubscriptionOnlyHappensOnce() throws InterruptedException { @@ -714,4 +714,83 @@ public void ambArrayOrder() { Flowable error = Flowable.error(new RuntimeException()); Flowable.ambArray(Flowable.just(1), error).test().assertValue(1).assertComplete(); } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerSuccessDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Flowable.ambArray( + Flowable.just(1) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Flowable.never() + ) + .subscribe(new Consumer() { + @Override + public void accept(Object v) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerErrorDispose() throws Exception { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Flowable.ambArray( + Flowable.error(ex) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Flowable.never() + ) + .subscribe(Functions.emptyConsumer(), new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerCompleteDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Flowable.ambArray( + Flowable.empty() + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Flowable.never() + ) + .subscribe(Functions.emptyConsumer(), Functions.emptyConsumer(), new Action() { + @Override + public void run() throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } } 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 76bd59591a..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; @@ -223,6 +222,7 @@ public boolean test(Integer i) { assertTrue(anyEven.blockingGet()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstream() { Flowable source = Flowable.just(1).isEmpty() @@ -489,6 +489,7 @@ public boolean test(Integer i) { assertTrue(anyEven.blockingFirst()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstreamFlowable() { Flowable source = Flowable.just(1).isEmpty() 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 23cf4ecf02..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; @@ -45,6 +44,7 @@ public void testHiding() { verify(subscriber).onComplete(); verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void testHidingError() { PublishProcessor src = PublishProcessor.create(); 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 aa8c3ef23c..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; @@ -496,6 +495,7 @@ public void bufferWithBOBoundaryThrows() { verify(subscriber, never()).onComplete(); verify(subscriber, never()).onNext(any()); } + @Test(timeout = 2000) public void bufferWithSizeTake1() { Flowable source = Flowable.just(1).repeat(); @@ -525,6 +525,7 @@ public void bufferWithSizeSkipTake1() { verify(subscriber).onComplete(); verify(subscriber, never()).onError(any(Throwable.class)); } + @Test(timeout = 2000) public void bufferWithTimeTake1() { Flowable source = Flowable.interval(40, 40, TimeUnit.MILLISECONDS, scheduler); @@ -541,6 +542,7 @@ public void bufferWithTimeTake1() { verify(subscriber).onComplete(); verify(subscriber, never()).onError(any(Throwable.class)); } + @Test(timeout = 2000) public void bufferWithTimeSkipTake2() { Flowable source = Flowable.interval(40, 40, TimeUnit.MILLISECONDS, scheduler); @@ -614,6 +616,7 @@ public void accept(List pv) { inOrder.verify(subscriber).onComplete(); verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void bufferWithSizeThrows() { PublishProcessor source = PublishProcessor.create(); @@ -683,6 +686,7 @@ public void bufferWithTimeAndSize() { inOrder.verify(subscriber).onComplete(); verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void bufferWithStartEndStartThrows() { PublishProcessor start = PublishProcessor.create(); @@ -711,6 +715,7 @@ public Flowable apply(Integer t1) { verify(subscriber, never()).onComplete(); verify(subscriber).onError(any(TestException.class)); } + @Test public void bufferWithStartEndEndFunctionThrows() { PublishProcessor start = PublishProcessor.create(); @@ -738,6 +743,7 @@ public Flowable apply(Integer t1) { verify(subscriber, never()).onComplete(); verify(subscriber).onError(any(TestException.class)); } + @Test public void bufferWithStartEndEndThrows() { PublishProcessor start = PublishProcessor.create(); @@ -881,7 +887,6 @@ public void cancel() { assertEquals(Long.MAX_VALUE, requested.get()); } - @Test public void testProducerRequestOverflowThroughBufferWithSize1() { TestSubscriber> ts = new TestSubscriber>(Long.MAX_VALUE >> 1); @@ -991,6 +996,7 @@ public void onNext(List t) { // FIXME I'm not sure why this is MAX_VALUE in 1.x because MAX_VALUE/2 is even and thus can't overflow when multiplied by 2 assertEquals(Long.MAX_VALUE - 1, requested.get()); } + @Test(timeout = 3000) public void testBufferWithTimeDoesntUnsubscribeDownstream() throws InterruptedException { final Subscriber subscriber = TestHelper.mockSubscriber(); @@ -1001,11 +1007,13 @@ public void testBufferWithTimeDoesntUnsubscribeDownstream() throws InterruptedEx public void onNext(Object t) { subscriber.onNext(t); } + @Override public void onError(Throwable e) { subscriber.onError(e); cdl.countDown(); } + @Override public void onComplete() { subscriber.onComplete(); @@ -2121,7 +2129,7 @@ public Integer apply(Integer integer, Long aLong) { return integer; } }) - .buffer(Flowable.interval(0,200, TimeUnit.MILLISECONDS), + .buffer(Flowable.interval(0, 200, TimeUnit.MILLISECONDS), new Function>() { @Override public Publisher apply(Long a) { @@ -2144,7 +2152,7 @@ public Integer apply(Integer integer, Long aLong) { return integer; } }) - .buffer(Flowable.interval(0,100, TimeUnit.MILLISECONDS), + .buffer(Flowable.interval(0, 100, TimeUnit.MILLISECONDS), new Function>() { @Override public Publisher apply(Long a) { @@ -2760,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/FlowableCacheTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCacheTest.java index c26e612e09..4208b18dec 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCacheTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCacheTest.java @@ -54,6 +54,7 @@ public void testColdReplayNoBackpressure() { assertEquals((Integer)i, onNextEvents.get(i)); } } + @Test public void testColdReplayBackpressure() { FlowableCache source = new FlowableCache(Flowable.range(0, 1000), 16); @@ -138,7 +139,7 @@ public void testUnsubscribeSource() throws Exception { f.subscribe(); f.subscribe(); f.subscribe(); - verify(unsubscribe, times(1)).run(); + verify(unsubscribe, never()).run(); } @Test @@ -178,6 +179,7 @@ public void testAsync() { assertEquals(10000, ts2.values().size()); } } + @Test public void testAsyncComeAndGo() { Flowable source = Flowable.interval(1, 1, TimeUnit.MILLISECONDS) @@ -244,7 +246,6 @@ public void testValuesAndThenError() { .concatWith(Flowable.error(new TestException())) .cache(); - TestSubscriber ts = new TestSubscriber(); source.subscribe(ts); 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 2934084b0d..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.*; @@ -807,8 +806,8 @@ public Long apply(Long t1, Integer t2) { public void testCombineLatestRequestOverflow() throws InterruptedException { @SuppressWarnings("unchecked") List> sources = Arrays.asList(Flowable.fromArray(1, 2, 3, 4), - Flowable.fromArray(5,6,7,8)); - Flowable f = Flowable.combineLatest(sources,new Function() { + Flowable.fromArray(5, 6, 7, 8)); + Flowable f = Flowable.combineLatest(sources, new Function() { @Override public Integer apply(Object[] args) { return (Integer) args[0]; @@ -1206,6 +1205,7 @@ public Object apply(Object[] a) throws Exception { .test() .assertFailure(TestException.class, "[1, 2]"); } + @SuppressWarnings("unchecked") @Test public void combineLatestEmpty() { diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatDelayErrorTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatDelayErrorTest.java index ab307df66f..7d343c1f3d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatDelayErrorTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatDelayErrorTest.java @@ -216,7 +216,6 @@ static Flowable withError(Flowable source) { return source.concatWith(Flowable.error(new TestException())); } - @Test public void concatDelayErrorFlowable() { TestSubscriber ts = TestSubscriber.create(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerTest.java index 3c77acb502..11f12fb4a3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerTest.java @@ -655,7 +655,6 @@ public Flowable apply(Integer t) { ts.assertValue(null); } - @Test public void testMaxConcurrent5() { final List requests = new ArrayList(); @@ -1334,4 +1333,37 @@ public void arrayDelayErrorMaxConcurrencyErrorDelayed() { ts.assertFailure(TestException.class, 1, 2); } + + @Test + public void cancelActive() { + PublishProcessor pp1 = PublishProcessor.create(); + PublishProcessor pp2 = PublishProcessor.create(); + + TestSubscriber ts = Flowable + .concatEager(Flowable.just(pp1, pp2)) + .test(); + + assertTrue(pp1.hasSubscribers()); + assertTrue(pp2.hasSubscribers()); + + ts.cancel(); + + assertFalse(pp1.hasSubscribers()); + assertFalse(pp2.hasSubscribers()); + } + + @Test + public void cancelNoInnerYet() { + PublishProcessor> pp1 = PublishProcessor.create(); + + TestSubscriber ts = Flowable + .concatEager(pp1) + .test(); + + assertTrue(pp1.hasSubscribers()); + + ts.cancel(); + + assertFalse(pp1.hasSubscribers()); + } } 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 c1ad560478..8bd29121a0 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java @@ -13,9 +13,21 @@ package io.reactivex.internal.operators.flowable; +import static org.junit.Assert.assertEquals; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + import org.junit.Test; +import org.reactivestreams.Publisher; -import io.reactivex.internal.operators.flowable.FlowableConcatMap.WeakScalarSubscription; +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +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; public class FlowableConcatMapTest { @@ -23,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); @@ -39,4 +51,223 @@ public void weakSubscriptionRequest() { ts.assertResult(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; + } + }) + .concatMap(new Function>() { + @Override + public Publisher apply(String v) + throws Exception { + return Flowable.just(v); + } + }) + .observeOn(Schedulers.computation()) + .distinct() + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult("RxSingleScheduler"); + } + + @Test + public void boundaryFusionDelayError() { + 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; + } + }) + .concatMapDelayError(new Function>() { + @Override + public Publisher apply(String v) + throws Exception { + return Flowable.just(v); + } + }) + .observeOn(Schedulers.computation()) + .distinct() + .test() + .awaitDone(5, TimeUnit.SECONDS) + .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) + .map(new Function() { + @Override + public Integer apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .compose(TestHelper.flowableStripBoundary()) + .concatMap(new Function>() { + @Override + public Publisher apply(Integer v) + throws Exception { + return Flowable.just(v); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void pollThrowsDelayError() { + Flowable.just(1) + .map(new Function() { + @Override + public Integer apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .compose(TestHelper.flowableStripBoundary()) + .concatMapDelayError(new Function>() { + @Override + public Publisher apply(Integer v) + throws Exception { + return Flowable.just(v); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void noCancelPrevious() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable.range(1, 5) + .concatMap(new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + return Flowable.just(v).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + + 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 7c53083548..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; @@ -29,7 +28,7 @@ import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.*; -import io.reactivex.functions.Function; +import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; @@ -158,7 +157,6 @@ public void testNestedAsyncConcat() throws InterruptedException { final CountDownLatch parentHasStarted = new CountDownLatch(1); final CountDownLatch parentHasFinished = new CountDownLatch(1); - Flowable> observableOfObservables = Flowable.unsafeCreate(new Publisher>() { @Override @@ -169,6 +167,7 @@ public void subscribe(final Subscriber> subscriber) { public void request(long n) { } + @Override public void cancel() { d.dispose(); @@ -636,6 +635,7 @@ public Flowable apply(Integer v) { inOrder.verify(o).onSuccess(list); verify(o, never()).onError(any(Throwable.class)); } + @Test public void concatVeryLongObservableOfObservablesTakeHalf() { final int n = 10000; @@ -778,8 +778,8 @@ public void onError(Throwable e) { @Test public void testRequestOverflowDoesNotStallStream() { - Flowable f1 = Flowable.just(1,2,3); - Flowable f2 = Flowable.just(4,5,6); + Flowable f1 = Flowable.just(1, 2, 3); + Flowable f2 = Flowable.just(4, 5, 6); final AtomicBoolean completed = new AtomicBoolean(false); f1.concatWith(f2).subscribe(new DefaultSubscriber() { @@ -1626,4 +1626,42 @@ public void subscribe(FlowableEmitter s) throws Exception { assertEquals(1, calls[0]); } + + @SuppressWarnings("unchecked") + @Test + public void noCancelPreviousArray() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable source = Flowable.just(1).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + Flowable.concatArray(source, source, source, source, source) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @SuppressWarnings("unchecked") + @Test + public void noCancelPreviousIterable() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable source = Flowable.just(1).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + Flowable.concat(Arrays.asList(source, source, source, source, source)) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybeTest.java index eb1fd62fe2..64a7c62149 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybeTest.java @@ -41,7 +41,6 @@ public void run() throws Exception { ts.assertResult(1, 2, 3, 4, 5, 100); } - @Test public void normalNonEmpty() { final TestSubscriber ts = new TestSubscriber(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCountTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCountTest.java index e11fb643eb..998569148d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCountTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCountTest.java @@ -39,7 +39,6 @@ public void simple() { } - @Test public void dispose() { TestHelper.checkDisposed(Flowable.just(1).count()); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCreateTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCreateTest.java index 8aeef598c8..899f145ca2 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCreateTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCreateTest.java @@ -1001,7 +1001,6 @@ public void cancel() throws Exception { } } - @Test public void tryOnError() { for (BackpressureStrategy strategy : BackpressureStrategy.values()) { 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 40348b0ee6..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; @@ -239,6 +238,7 @@ public Flowable apply(Integer t1) { verify(subscriber, never()).onComplete(); verify(subscriber).onError(any(TestException.class)); } + @Test public void debounceTimedLastIsNotLost() { PublishProcessor source = PublishProcessor.create(); @@ -256,6 +256,7 @@ public void debounceTimedLastIsNotLost() { verify(subscriber).onComplete(); verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void debounceSelectorLastIsNotLost() { PublishProcessor source = PublishProcessor.create(); @@ -545,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/FlowableDefaultIfEmptyTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDefaultIfEmptyTest.java index fa6b123a15..4c75d500e8 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDefaultIfEmptyTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDefaultIfEmptyTest.java @@ -97,7 +97,7 @@ public void testBackpressureEmpty() { @Test public void testBackpressureNonEmpty() { TestSubscriber ts = new TestSubscriber(0L); - Flowable.just(1,2,3).defaultIfEmpty(1).subscribe(ts); + Flowable.just(1, 2, 3).defaultIfEmpty(1).subscribe(ts); ts.assertNoValues(); ts.assertNotTerminated(); ts.request(2); 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 f83510f830..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; @@ -24,12 +23,57 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subscribers.TestSubscriber; +@SuppressWarnings("deprecation") public class FlowableDematerializeTest { + @Test + public void simpleSelector() { + Flowable> notifications = Flowable.just(1, 2).materialize(); + Flowable dematerialize = notifications.dematerialize(Functions.>identity()); + + Subscriber subscriber = TestHelper.mockSubscriber(); + + dematerialize.subscribe(subscriber); + + verify(subscriber, times(1)).onNext(1); + verify(subscriber, times(1)).onNext(2); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + } + + @Test + public void selectorCrash() { + Flowable.just(1, 2) + .materialize() + .dematerialize(new Function, Notification>() { + @Override + public Notification apply(Notification v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void selectorNull() { + Flowable.just(1, 2) + .materialize() + .dematerialize(new Function, Notification>() { + @Override + public Notification apply(Notification v) throws Exception { + return null; + } + }) + .test() + .assertFailure(NullPointerException.class); + } + @Test public void testDematerialize1() { Flowable> notifications = Flowable.just(1, 2).materialize(); @@ -176,4 +220,19 @@ protected void subscribeActual(Subscriber subscriber) { RxJavaPlugins.reset(); } } + + @Test + public void nonNotificationInstanceAfterDispose() { + new Flowable() { + @Override + protected void subscribeActual(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(Notification.createOnComplete()); + subscriber.onNext(1); + } + } + .dematerialize() + .test() + .assertResult(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDetachTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDetachTest.java index a42c0d889f..2b5bcea1e6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDetachTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDetachTest.java @@ -26,7 +26,6 @@ import io.reactivex.functions.Function; import io.reactivex.subscribers.TestSubscriber; - public class FlowableDetachTest { Object o; @@ -87,7 +86,6 @@ public void range() { ts.assertComplete(); } - @Test public void backpressured() throws Exception { o = new Object(); 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 2e5e97bb67..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; @@ -234,7 +233,7 @@ public void accept(Throwable t) { @Test public void customComparator() { - Flowable source = Flowable.just("a", "b", "B", "A","a", "C"); + Flowable source = Flowable.just("a", "b", "B", "A", "a", "C"); TestSubscriber ts = TestSubscriber.create(); @@ -253,7 +252,7 @@ public boolean test(String a, String b) { @Test public void customComparatorThrows() { - Flowable source = Flowable.just("a", "b", "B", "A","a", "C"); + Flowable source = Flowable.just("a", "b", "B", "A", "a", "C"); TestSubscriber ts = TestSubscriber.create(); @@ -469,7 +468,6 @@ public boolean test(Integer v) throws Exception { }) .subscribe(ts); - TestHelper.emit(up, 1, 2, 1, 3, 3, 4, 3, 5, 5); SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoFinallyTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoFinallyTest.java index 943d92ff03..93dd960cf3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoFinallyTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoFinallyTest.java @@ -157,7 +157,6 @@ public void asyncFusedBoundary() { assertEquals(1, calls); } - @Test public void normalJustConditional() { Flowable.just(1) 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/FlowableDoOnLifecycleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycleTest.java index 3d23930aaa..34578bbfee 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycleTest.java @@ -87,7 +87,7 @@ public void run() throws Exception { ); assertEquals(1, calls[0]); - assertEquals(2, calls[1]); + assertEquals(1, calls[1]); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnRequestTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnRequestTest.java index 1d30f4663c..4502206206 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnRequestTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnRequestTest.java @@ -83,7 +83,7 @@ public void onNext(Integer t) { request(t); } }); - assertEquals(Arrays.asList(3L,1L,2L,3L,4L,5L), requests); + assertEquals(Arrays.asList(3L, 1L, 2L, 3L, 4L, 5L), requests); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnUnsubscribeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnUnsubscribeTest.java index 2987102666..de1bd0d57a 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnUnsubscribeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnUnsubscribeTest.java @@ -24,6 +24,7 @@ import io.reactivex.Flowable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.*; +import io.reactivex.processors.BehaviorProcessor; import io.reactivex.subscribers.TestSubscriber; public class FlowableDoOnUnsubscribeTest { @@ -148,4 +149,24 @@ public void run() { assertEquals("There should exactly 1 un-subscription events for upper stream", 1, upperCount.get()); assertEquals("There should exactly 1 un-subscription events for lower stream", 1, lowerCount.get()); } + + @Test + public void noReentrantDispose() { + + final AtomicInteger cancelCalled = new AtomicInteger(); + + final BehaviorProcessor p = BehaviorProcessor.create(); + p.doOnCancel(new Action() { + @Override + public void run() throws Exception { + cancelCalled.incrementAndGet(); + p.onNext(2); + } + }) + .firstOrError() + .subscribe() + .dispose(); + + assertEquals(1, cancelCalled.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableElementAtTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableElementAtTest.java index e8a395fc3b..d5665d6bf3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableElementAtTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableElementAtTest.java @@ -186,7 +186,6 @@ public void elementAtOrErrorIndex1OnEmptySource() { .assertFailure(NoSuchElementException.class); } - @Test public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function, Publisher>() { @@ -229,7 +228,6 @@ public void errorFlowable() { .assertFailure(TestException.class); } - @Test public void error() { Flowable.error(new TestException()) 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/FlowableFlatMapCompletableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableTest.java index 01309ff55c..62cee79161 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableTest.java @@ -183,7 +183,6 @@ public CompletableSource apply(Integer v) throws Exception { .assertFailure(TestException.class); } - @Test public void fusedFlowable() { TestSubscriber ts = SubscriberFusion.newTest(QueueFuseable.ANY); @@ -338,7 +337,6 @@ public CompletableSource apply(Integer v) throws Exception { .assertFailure(TestException.class); } - @Test public void fused() { TestSubscriber ts = SubscriberFusion.newTest(QueueFuseable.ANY); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybeTest.java index eac6c0503f..aab0940c46 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybeTest.java @@ -261,7 +261,8 @@ public MaybeSource apply(Integer v) throws Exception { @Test public void middleError() { - Flowable.fromArray(new String[]{"1","a","2"}).flatMapMaybe(new Function>() { + Flowable.fromArray(new String[]{"1", "a", "2"}) + .flatMapMaybe(new Function>() { @Override public MaybeSource apply(final String s) throws NumberFormatException { //return Maybe.just(Integer.valueOf(s)); //This works diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingleTest.java index 381d210070..ac137d6c3d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingleTest.java @@ -248,7 +248,8 @@ public SingleSource apply(Integer v) throws Exception { @Test public void middleError() { - Flowable.fromArray(new String[]{"1","a","2"}).flatMapSingle(new Function>() { + Flowable.fromArray(new String[]{"1", "a", "2"}) + .flatMapSingle(new Function>() { @Override public SingleSource apply(final String s) throws NumberFormatException { //return Single.just(Integer.valueOf(s)); //This works 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 9cdbfc26af..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.*; @@ -203,7 +202,6 @@ public void testFlatMapTransformsException() { Flowable. error(new RuntimeException("Forced failure!")) ); - Subscriber subscriber = TestHelper.mockSubscriber(); source.flatMap(just(onNext), just(onError), just0(onComplete)).subscribe(subscriber); @@ -355,6 +353,7 @@ public Flowable apply(Integer t1) { Assert.assertEquals(expected.size(), ts.valueCount()); Assert.assertTrue(expected.containsAll(ts.values())); } + @Test public void testFlatMapSelectorMaxConcurrent() { final int m = 4; @@ -478,6 +477,7 @@ public Flowable apply(Integer t) { } } } + @Test(timeout = 30000) public void flatMapRangeMixedAsyncLoop() { for (int i = 0; i < 2000; i++) { @@ -521,7 +521,7 @@ public Flowable apply(Integer t) { @Test public void flatMapIntPassthruAsync() { - for (int i = 0;i < 1000; i++) { + for (int i = 0; i < 1000; i++) { TestSubscriber ts = new TestSubscriber(); Flowable.range(1, 1000).flatMap(new Function>() { @@ -537,6 +537,7 @@ public Flowable apply(Integer t) { ts.assertValueCount(1000); } } + @Test public void flatMapTwoNestedSync() { for (final int n : new int[] { 1, 1000, 1000000 }) { @@ -856,7 +857,6 @@ public void run() { } } - @Test public void fusedInnerThrows() { Flowable.just(1).hide() @@ -1083,4 +1083,101 @@ public void remove() { assertEquals(1, counter.get()); } + + @Test + public void maxConcurrencySustained() { + final PublishProcessor pp1 = PublishProcessor.create(); + final PublishProcessor pp2 = PublishProcessor.create(); + PublishProcessor pp3 = PublishProcessor.create(); + PublishProcessor pp4 = PublishProcessor.create(); + + TestSubscriber ts = Flowable.just(pp1, pp2, pp3, pp4) + .flatMap(new Function, Flowable>() { + @Override + public Flowable apply(PublishProcessor v) throws Exception { + return v; + } + }, 2) + .doOnNext(new Consumer() { + @Override + public void accept(Integer v) throws Exception { + if (v == 1) { + // this will make sure the drain loop detects two completed + // inner sources and replaces them with fresh ones + pp1.onComplete(); + pp2.onComplete(); + } + } + }) + .test(); + + pp1.onNext(1); + + assertFalse(pp1.hasSubscribers()); + assertFalse(pp2.hasSubscribers()); + assertTrue(pp3.hasSubscribers()); + assertTrue(pp4.hasSubscribers()); + + ts.dispose(); + + 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/FlowableFromArrayTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromArrayTest.java index b07fe3bf2d..2112541c29 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromArrayTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromArrayTest.java @@ -33,6 +33,7 @@ Flowable create(int n) { } return Flowable.fromArray(array); } + @Test public void simple() { TestSubscriber ts = new TestSubscriber(); 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 f3f5e00fa5..254274b1cf 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromCallableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromCallableTest.java @@ -16,9 +16,10 @@ package io.reactivex.internal.operators.flowable; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; -import static org.junit.Assert.*; +import java.util.List; import java.util.concurrent.*; import org.junit.Test; @@ -27,7 +28,9 @@ import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -238,4 +241,27 @@ public Object call() throws Exception { .test() .assertFailure(NullPointerException.class); } + + @Test(timeout = 5000) + public void undeliverableUponCancellation() throws Exception { + List errors = TestHelper.trackPluginErrors(); + try { + final TestSubscriber ts = new TestSubscriber(); + + Flowable.fromCallable(new Callable() { + @Override + public Integer call() throws Exception { + ts.cancel(); + throw new TestException(); + } + }) + .subscribe(ts); + + ts.assertEmpty(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } } 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 fdedc6577c..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.*; @@ -172,7 +171,7 @@ public void testSubscribeMultipleTimes() { @Test public void testFromIterableRequestOverflow() throws InterruptedException { - Flowable f = Flowable.fromIterable(Arrays.asList(1,2,3,4)); + Flowable f = Flowable.fromIterable(Arrays.asList(1, 2, 3, 4)); final int expectedCount = 4; final CountDownLatch latch = new CountDownLatch(expectedCount); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromSourceTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromSourceTest.java index e1f15bb261..b2452a65fb 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromSourceTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromSourceTest.java @@ -40,7 +40,6 @@ public void before() { ts = new TestSubscriber(0L); } - @Test public void normalBuffered() { Flowable.create(source, BackpressureStrategy.BUFFER).subscribe(ts); @@ -125,7 +124,6 @@ public void normalMissingRequested() { ts.assertComplete(); } - @Test public void normalError() { Flowable.create(source, BackpressureStrategy.ERROR).subscribe(ts); @@ -489,7 +487,6 @@ public void unsubscribeNoCancel() { ts.assertNotComplete(); } - @Test public void unsubscribeInline() { TestSubscriber ts1 = new TestSubscriber() { 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 022ab3788f..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; @@ -1638,7 +1639,6 @@ public void accept(GroupedFlowable g) { .assertComplete(); } - @Test public void keySelectorAndDelayError() { Flowable.just(1).concatWith(Flowable.error(new TestException())) @@ -2070,10 +2070,10 @@ public Publisher apply(GroupedFlowable g) th } //not thread safe - private static final class SingleThreadEvictingHashMap implements Map { + private static final class SingleThreadEvictingHashMap implements Map { private final List list = new ArrayList(); - private final Map map = new HashMap(); + private final Map map = new HashMap(); private final int maxSize; private final Consumer evictedListener; @@ -2176,7 +2176,7 @@ public Map apply(final Consumer notify) throws Exceptio .maximumSize(maxSize) // .removalListener(new RemovalListener() { @Override - public void onRemoval(RemovalNotification notification) { + public void onRemoval(RemovalNotification notification) { try { notify.accept(notification.getValue()); } catch (Exception e) { @@ -2195,7 +2195,7 @@ private static Function, Map> createEvictingMa @Override public Map apply(final Consumer notify) throws Exception { - return new SingleThreadEvictingHashMap(maxSize, new Consumer() { + return new SingleThreadEvictingHashMap(maxSize, new Consumer() { @Override public void accept(Object object) { try { @@ -2207,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/FlowableIgnoreElementsTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsTest.java index 8656afde8d..1f6c9e23a5 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsTest.java @@ -144,7 +144,6 @@ public void onNext(Integer t) { assertEquals(0, count.get()); } - @Test public void testWithEmpty() { assertNull(Flowable.empty().ignoreElements().blockingGet()); 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 03300d4dda..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; @@ -436,6 +435,7 @@ public void onNext(String args) { } } + @Test @Ignore("Subscribers should not throw") public void testMergeSourceWhichDoesntPropagateExceptionBack() { @@ -559,6 +559,7 @@ public void run() { t.start(); } } + @Test public void testDelayErrorMaxConcurrent() { final List requests = new ArrayList(); @@ -737,7 +738,6 @@ public void array() { } } - @SuppressWarnings("unchecked") @Test public void mergeArrayDelayError() { diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeMaxConcurrentTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeMaxConcurrentTest.java index 0973027f3b..ef5dd519ff 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeMaxConcurrentTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeMaxConcurrentTest.java @@ -129,6 +129,7 @@ public void testMergeALotOfSourcesOneByOneSynchronously() { } assertEquals(j, n); } + @Test public void testMergeALotOfSourcesOneByOneSynchronouslyTakeHalf() { int n = 10000; @@ -163,6 +164,7 @@ public void testSimple() { ts.assertValueSequence(result); } } + @Test public void testSimpleOneLess() { for (int i = 2; i < 100; i++) { @@ -181,6 +183,7 @@ public void testSimpleOneLess() { ts.assertValueSequence(result); } } + @Test//(timeout = 20000) public void testSimpleAsyncLoop() { IoScheduler ios = (IoScheduler)Schedulers.io(); @@ -193,6 +196,7 @@ public void testSimpleAsyncLoop() { } } } + @Test(timeout = 10000) public void testSimpleAsync() { for (int i = 1; i < 50; i++) { @@ -213,12 +217,14 @@ public void testSimpleAsync() { assertEquals(expected, actual); } } + @Test(timeout = 10000) public void testSimpleOneLessAsyncLoop() { for (int i = 0; i < 200; i++) { testSimpleOneLessAsync(); } } + @Test(timeout = 10000) public void testSimpleOneLessAsync() { long t = System.currentTimeMillis(); @@ -243,6 +249,7 @@ public void testSimpleOneLessAsync() { assertEquals(expected, actual); } } + @Test(timeout = 5000) public void testBackpressureHonored() throws Exception { List> sourceList = new ArrayList>(3); @@ -273,6 +280,7 @@ public void onNext(Integer t) { ts.dispose(); } + @Test(timeout = 5000) public void testTake() throws Exception { List> sourceList = new ArrayList>(3); 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 e66206c865..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; @@ -335,8 +334,8 @@ public void testError2() { // we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior final Flowable f1 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); final Flowable f2 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" - final Flowable f3 = Flowable.unsafeCreate(new TestErrorFlowable("seven", "eight", null));// we expect to lose all of these since o2 is done first and fails - final Flowable f4 = Flowable.unsafeCreate(new TestErrorFlowable("nine"));// we expect to lose all of these since o2 is done first and fails + final Flowable f3 = Flowable.unsafeCreate(new TestErrorFlowable("seven", "eight", null)); // we expect to lose all of these since o2 is done first and fails + final Flowable f4 = Flowable.unsafeCreate(new TestErrorFlowable("nine")); // we expect to lose all of these since o2 is done first and fails Flowable m = Flowable.merge(f1, f2, f3, f4); m.subscribe(stringSubscriber); @@ -1269,8 +1268,8 @@ public void run() { @Test public void testMergeRequestOverflow() throws InterruptedException { //do a non-trivial merge so that future optimisations with EMPTY don't invalidate this test - Flowable f = Flowable.fromIterable(Arrays.asList(1,2)) - .mergeWith(Flowable.fromIterable(Arrays.asList(3,4))); + Flowable f = Flowable.fromIterable(Arrays.asList(1, 2)) + .mergeWith(Flowable.fromIterable(Arrays.asList(3, 4))); final int expectedCount = 4; final CountDownLatch latch = new CountDownLatch(expectedCount); f.subscribeOn(Schedulers.computation()).subscribe(new DefaultSubscriber() { @@ -1360,10 +1359,12 @@ void runMerge(Function> func, TestSubscriber public void testFastMergeFullScalar() { runMerge(toScalar, new TestSubscriber()); } + @Test public void testFastMergeHiddenScalar() { runMerge(toHiddenScalar, new TestSubscriber()); } + @Test public void testSlowMergeFullScalar() { for (final int req : new int[] { 16, 32, 64, 128, 256 }) { @@ -1382,6 +1383,7 @@ public void onNext(Integer t) { runMerge(toScalar, ts); } } + @Test public void testSlowMergeHiddenScalar() { for (final int req : new int[] { 16, 32, 64, 128, 256 }) { @@ -1462,7 +1464,6 @@ public void mergeConcurrentJustRange() { ts.assertComplete(); } - @SuppressWarnings("unchecked") @Test @Ignore("No 2-9 argument merge()") @@ -1622,7 +1623,6 @@ public void array() { } } - @SuppressWarnings("unchecked") @Test public void mergeArray() { 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 10c1facc16..a5abff03ef 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableObserveOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableObserveOnTest.java @@ -21,12 +21,13 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import io.reactivex.annotations.Nullable; import org.junit.Test; import org.mockito.InOrder; import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; @@ -34,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.*; @@ -794,12 +796,11 @@ public void onNext(Integer t) { assertEquals(Arrays.asList(128L), requests); } - @Test public void testErrorDelayed() { TestScheduler s = new TestScheduler(); - Flowable source = Flowable.just(1, 2 ,3) + Flowable source = Flowable.just(1, 2, 3) .concatWith(Flowable.error(new TestException())); TestSubscriber ts = TestSubscriber.create(0); @@ -833,7 +834,7 @@ public void testErrorDelayed() { @Test public void testErrorDelayedAsync() { - Flowable source = Flowable.just(1, 2 ,3) + Flowable source = Flowable.just(1, 2, 3) .concatWith(Flowable.error(new TestException())); TestSubscriber ts = TestSubscriber.create(); @@ -1782,4 +1783,196 @@ public void syncFusedRequestOneByOneConditional() { .test() .assertResult(1, 2, 3, 4, 5); } + + public static final class DisposeTrackingScheduler extends Scheduler { + + public final AtomicInteger disposedCount = new AtomicInteger(); + + @Override + public Worker createWorker() { + return new TrackingWorker(); + } + + final class TrackingWorker extends Scheduler.Worker { + + @Override + public void dispose() { + disposedCount.getAndIncrement(); + } + + @Override + public boolean isDisposed() { + return false; + } + + @Override + public Disposable schedule(Runnable run, long delay, + TimeUnit unit) { + run.run(); + return Disposables.empty(); + } + } + } + + @Test + public void workerNotDisposedPrematurelyNormalInNormalOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + Flowable.concat( + Flowable.just(1).hide().observeOn(s), + Flowable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelySyncInNormalOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + Flowable.concat( + Flowable.just(1).observeOn(s), + Flowable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelyAsyncInNormalOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + UnicastProcessor up = UnicastProcessor.create(); + up.onNext(1); + up.onComplete(); + + Flowable.concat( + up.observeOn(s), + Flowable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + static final class TestSubscriberFusedCanceling + extends TestSubscriber { + + TestSubscriberFusedCanceling() { + super(); + initialFusionMode = QueueFuseable.ANY; + } + + @Override + public void onComplete() { + cancel(); + super.onComplete(); + } + } + + @Test + public void workerNotDisposedPrematurelyNormalInAsyncOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + TestSubscriber ts = new TestSubscriberFusedCanceling(); + + Flowable.just(1).hide().observeOn(s).subscribe(ts); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelyNormalInNormalOutConditional() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + Flowable.concat( + Flowable.just(1).hide().observeOn(s).filter(Functions.alwaysTrue()), + Flowable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelySyncInNormalOutConditional() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + Flowable.concat( + Flowable.just(1).observeOn(s).filter(Functions.alwaysTrue()), + Flowable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelyAsyncInNormalOutConditional() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + UnicastProcessor up = UnicastProcessor.create(); + up.onNext(1); + up.onComplete(); + + Flowable.concat( + up.observeOn(s).filter(Functions.alwaysTrue()), + Flowable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelyNormalInAsyncOutConditional() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + TestSubscriber ts = new TestSubscriberFusedCanceling(); + + Flowable.just(1).hide().observeOn(s).filter(Functions.alwaysTrue()).subscribe(ts); + + 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/FlowableOnBackpressureBufferStrategyTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategyTest.java index 85468f8753..c52888548d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategyTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategyTest.java @@ -116,7 +116,6 @@ public void subscribe(Subscriber s) { } }); - @Test(expected = IllegalArgumentException.class) public void backpressureBufferNegativeCapacity() throws InterruptedException { Flowable.empty().onBackpressureBuffer(-1, EMPTY_ACTION , DROP_OLDEST); 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/FlowableOnBackpressureLatestTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatestTest.java index 6fb70da88b..db6b3d9580 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatestTest.java @@ -37,6 +37,7 @@ public void testSimple() { ts.assertTerminated(); ts.assertValues(1, 2, 3, 4, 5); } + @Test public void testSimpleError() { TestSubscriber ts = new TestSubscriber(); @@ -48,6 +49,7 @@ public void testSimpleError() { ts.assertError(TestException.class); ts.assertValues(1, 2, 3, 4, 5); } + @Test public void testSimpleBackpressure() { TestSubscriber ts = new TestSubscriber(2L); @@ -100,6 +102,7 @@ public void testSynchronousDrop() { ts.assertNoErrors(); ts.assertTerminated(); } + @Test public void testAsynchronousDrop() throws InterruptedException { TestSubscriber ts = new TestSubscriber(1L) { 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/FlowableOnErrorReturnTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturnTest.java index 9bcc27d5f3..eb2f120e43 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturnTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturnTest.java @@ -242,7 +242,6 @@ public Integer apply(Throwable e) { ts.assertComplete(); } - @Test public void returnItem() { Flowable.error(new TestException()) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnExceptionResumeNextViaFlowableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnExceptionResumeNextViaFlowableTest.java index 3da05ffedf..ee1b4e3051 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnExceptionResumeNextViaFlowableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnExceptionResumeNextViaFlowableTest.java @@ -186,7 +186,6 @@ public String apply(String s) { verify(subscriber, times(1)).onComplete(); } - @Test public void testBackpressure() { TestSubscriber ts = new TestSubscriber(); @@ -215,7 +214,6 @@ public Integer apply(Integer t1) { ts.assertNoErrors(); } - private static class TestObservable implements Publisher { final String[] values; 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/FlowablePublishFunctionTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishFunctionTest.java index 039114d1c7..e744a00aa1 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishFunctionTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishFunctionTest.java @@ -33,7 +33,6 @@ import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; - public class FlowablePublishFunctionTest { @Test public void concatTakeFirstLastCompletes() { diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticastTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticastTest.java index 8e004d7af7..4078b5b0b0 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticastTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticastTest.java @@ -152,7 +152,6 @@ public void errorAllCancelled() { mp.errorAll(null); } - @Test public void completeAllCancelled() { MulticastProcessor mp = new MulticastProcessor(128, true); 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 bf8b269196..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(); @@ -371,6 +393,7 @@ public void testZeroRequested() { ts.assertNoErrors(); ts.assertTerminated(); } + @Test public void testConnectIsIdempotent() { final AtomicInteger calls = new AtomicInteger(); @@ -824,12 +847,36 @@ 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(); @@ -1316,4 +1363,161 @@ public void onComplete() { 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) + ); + } } 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 f97febd9e2..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.*; @@ -164,18 +163,21 @@ void testWithBackpressureAllAtOnce(long start) { ts.assertValueSequence(list); ts.assertTerminated(); } + @Test public void testWithBackpressure1() { for (long i = 0; i < 100; i++) { testWithBackpressureOneByOne(i); } } + @Test public void testWithBackpressureAllAtOnce() { for (long i = 0; i < 100; i++) { testWithBackpressureAllAtOnce(i); } } + @Test public void testWithBackpressureRequestWayMore() { Flowable source = Flowable.rangeLong(50, 100); 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 36a345c617..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.*; @@ -164,18 +163,21 @@ void testWithBackpressureAllAtOnce(int start) { ts.assertValueSequence(list); ts.assertTerminated(); } + @Test public void testWithBackpressure1() { for (int i = 0; i < 100; i++) { testWithBackpressureOneByOne(i); } } + @Test public void testWithBackpressureAllAtOnce() { for (int i = 0; i < 100; i++) { testWithBackpressureAllAtOnce(i); } } + @Test public void testWithBackpressureRequestWayMore() { Flowable source = Flowable.range(50, 100); @@ -260,6 +262,7 @@ public void testNearMaxValueWithoutBackpressure() { ts.assertNoErrors(); ts.assertValues(Integer.MAX_VALUE - 1, Integer.MAX_VALUE); } + @Test(timeout = 1000) public void testNearMaxValueWithBackpressure() { TestSubscriber ts = new TestSubscriber(3L); @@ -270,7 +273,6 @@ public void testNearMaxValueWithBackpressure() { ts.assertValues(Integer.MAX_VALUE - 1, Integer.MAX_VALUE); } - @Test public void negativeCount() { try { 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 12425aed42..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; @@ -141,7 +140,6 @@ public void testBackpressureWithInitialValueFlowable() throws InterruptedExcepti assertEquals(21, r.intValue()); } - @Test public void testAggregateAsIntSum() { @@ -389,7 +387,8 @@ public Integer apply(Integer a, Integer b) throws Exception { } /** - * https://gist.github.com/jurna/353a2bd8ff83f0b24f0b5bc772077d61 + * Make sure an asynchronous reduce with flatMap works. + * Original Reactor-Core test case: https://gist.github.com/jurna/353a2bd8ff83f0b24f0b5bc772077d61 */ @Test public void shouldReduceTo10Events() { @@ -415,7 +414,8 @@ public String apply(String l, String r) throws Exception { @Override public void accept(String s) throws Exception { count.incrementAndGet(); - System.out.println("Completed with " + s);} + System.out.println("Completed with " + s); + } }) .toFlowable(); } @@ -426,7 +426,8 @@ public void accept(String s) throws Exception { } /** - * https://gist.github.com/jurna/353a2bd8ff83f0b24f0b5bc772077d61 + * Make sure an asynchronous reduce with flatMap works. + * Original Reactor-Core test case: https://gist.github.com/jurna/353a2bd8ff83f0b24f0b5bc772077d61 */ @Test public void shouldReduceTo10EventsFlowable() { @@ -453,7 +454,8 @@ public String apply(String l, String r) throws Exception { @Override public void accept(String s) throws Exception { count.incrementAndGet(); - System.out.println("Completed with " + s);} + System.out.println("Completed with " + s); + } }) ; } 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 8eefc701e3..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(); @@ -537,7 +559,7 @@ public void testUpstreamErrorAllowsRetry() throws InterruptedException { try { final AtomicInteger intervalSubscribed = new AtomicInteger(); Flowable interval = - Flowable.interval(200,TimeUnit.MILLISECONDS) + Flowable.interval(200, TimeUnit.MILLISECONDS) .doOnSubscribe(new Consumer() { @Override public void accept(Subscription s) { @@ -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); @@ -788,15 +816,17 @@ public void replayIsUnsubscribed() { ConnectableFlowable cf = Flowable.just(1) .replay(); - assertTrue(((Disposable)cf).isDisposed()); + if (cf instanceof Disposable) { + assertTrue(((Disposable)cf).isDisposed()); - Disposable connection = cf.connect(); + Disposable connection = cf.connect(); - assertFalse(((Disposable)cf).isDisposed()); + assertFalse(((Disposable)cf).isDisposed()); - connection.dispose(); + connection.dispose(); - assertTrue(((Disposable)cf).isDisposed()); + assertTrue(((Disposable)cf).isDisposed()); + } } static final class BadFlowableSubscribe extends ConnectableFlowable { @@ -1325,5 +1355,105 @@ public void cancelTerminateStateExclusion() { rc.connected = true; o.connection = rc; 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 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 82cec69233..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.*; @@ -29,6 +28,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -368,4 +368,129 @@ public Flowable apply(Flowable handler) throws Exception { .test() .assertResult(1, 2, 3, 1, 2, 3); } + + @Test + public void noCancelPreviousRepeat() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable source = Flowable.just(1).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.repeat(5) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatUntil() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable source = Flowable.just(1).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + final AtomicInteger times = new AtomicInteger(); + + source.repeatUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return times.getAndIncrement() == 4; + } + }) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatWhen() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable source = Flowable.just(1).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + final AtomicInteger times = new AtomicInteger(); + + source.repeatWhen(new Function, Flowable>() { + @Override + public Flowable apply(Flowable e) throws Exception { + return e.takeWhile(new Predicate() { + @Override + public boolean test(Object v) throws Exception { + return times.getAndIncrement() < 4; + } + }); + } + }) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @Test + public void repeatFloodNoSubscriptionError() { + List errors = TestHelper.trackPluginErrors(); + + try { + final PublishProcessor source = PublishProcessor.create(); + final PublishProcessor signaller = PublishProcessor.create(); + + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + TestSubscriber ts = source.take(1) + .repeatWhen(new Function, Flowable>() { + @Override + public Flowable apply(Flowable v) + throws Exception { + return signaller; + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + source.onNext(1); + } + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + signaller.offer(1); + } + } + }; + + TestHelper.race(r1, r2); + + ts.dispose(); + } + + if (!errors.isEmpty()) { + for (Throwable e : errors) { + e.printStackTrace(); + } + fail(errors + ""); + } + } finally { + RxJavaPlugins.reset(); + } + } } 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 137565dae4..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,9 +14,9 @@ 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.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; @@ -506,7 +506,6 @@ public void run() { } } - /* * test the basic expectation of OperatorMulticast via replay */ @@ -521,7 +520,7 @@ public void testIssue2191_UnsubscribeSource() throws Exception { Subscriber spiedSubscriberAfterConnect = TestHelper.mockSubscriber(); // Flowable under test - Flowable source = Flowable.just(1,2); + Flowable source = Flowable.just(1, 2); ConnectableFlowable replay = source .doOnNext(sourceNext) @@ -696,7 +695,6 @@ public static Worker workerSpy(final Disposable mockDisposable) { return spy(new InprocessWorker(mockDisposable)); } - private static class InprocessWorker extends Worker { private final Disposable mockDisposable; public boolean unsubscribed; @@ -876,6 +874,7 @@ public void testColdReplayNoBackpressure() { assertEquals((Integer)i, onNextEvents.get(i)); } } + @Test public void testColdReplayBackpressure() { Flowable source = Flowable.range(0, 1000).replay().autoConnect(); @@ -952,11 +951,11 @@ public void accept(String v) { @Test public void testUnsubscribeSource() throws Exception { Action unsubscribe = mock(Action.class); - Flowable f = Flowable.just(1).doOnCancel(unsubscribe).cache(); + Flowable f = Flowable.just(1).doOnCancel(unsubscribe).replay().autoConnect(); f.subscribe(); f.subscribe(); f.subscribe(); - verify(unsubscribe, times(1)).run(); + verify(unsubscribe, never()).run(); } @Test @@ -995,6 +994,7 @@ public void testAsync() { assertEquals(10000, ts2.values().size()); } } + @Test public void testAsyncComeAndGo() { Flowable source = Flowable.interval(1, 1, TimeUnit.MILLISECONDS) @@ -1061,7 +1061,6 @@ public void testValuesAndThenError() { .concatWith(Flowable.error(new TestException())) .replay().autoConnect(); - TestSubscriber ts = new TestSubscriber(); source.subscribe(ts); @@ -1167,7 +1166,6 @@ public void testSubscribersComeAndGoAtRequestBoundaries() { ts22.assertNoErrors(); ts22.dispose(); - TestSubscriber ts3 = new TestSubscriber(); source.subscribe(ts3); @@ -1223,7 +1221,6 @@ public void testSubscribersComeAndGoAtRequestBoundaries2() { ts22.assertNoErrors(); ts22.dispose(); - TestSubscriber ts3 = new TestSubscriber(); source.subscribe(ts3); @@ -1979,4 +1976,67 @@ public void currentDisposedWhenConnecting() { assertFalse(fr.current.get().isDisposed()); } + + @Test + public void noBoundedRetentionViaThreadLocal() throws Exception { + Flowable source = Flowable.range(1, 200) + .map(new Function() { + @Override + public byte[] apply(Integer v) throws Exception { + return new byte[1024 * 1024]; + } + }) + .replay(new Function, Publisher>() { + @Override + public Publisher apply(final Flowable f) throws Exception { + return f.take(1) + .concatMap(new Function>() { + @Override + public Publisher apply(byte[] v) throws Exception { + return f; + } + }); + } + }, 1) + .takeLast(1) + ; + + System.out.println("Bounded Replay Leak check: Wait before GC"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC"); + System.gc(); + + Thread.sleep(500); + + final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); + long initial = memHeap.getUsed(); + + System.out.printf("Bounded Replay Leak check: Starting: %.3f MB%n", initial / 1024.0 / 1024.0); + + final AtomicLong after = new AtomicLong(); + + source.subscribe(new Consumer() { + @Override + public void accept(byte[] v) throws Exception { + System.out.println("Bounded Replay Leak check: Wait before GC 2"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC 2"); + System.gc(); + + Thread.sleep(500); + + after.set(memoryMXBean.getHeapMemoryUsage().getUsed()); + } + }); + + System.out.printf("Bounded Replay Leak check: After: %.3f MB%n", after.get() / 1024.0 / 1024.0); + + if (initial + 100 * 1024 * 1024 < after.get()) { + Assert.fail("Bounded Replay Leak check: Memory leak detected: " + (initial / 1024.0 / 1024.0) + + " -> " + after.get() / 1024.0 / 1024.0); + } + } } 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 e7a2bb8eaa..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.*; @@ -30,7 +29,9 @@ import io.reactivex.exceptions.TestException; import io.reactivex.flowables.GroupedFlowable; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.*; @@ -355,7 +356,7 @@ public void testRetrySubscribesAgainAfterError() throws Exception { Consumer throwException = mock(Consumer.class); doThrow(new RuntimeException()).when(throwException).accept(Mockito.anyInt()); - // create a retrying observable based on a PublishProcessor + // create a retrying Flowable based on a PublishProcessor PublishProcessor processor = PublishProcessor.create(); processor // record item @@ -433,6 +434,7 @@ public void request(long n) { } } } + @Override public void cancel() { // TODO Auto-generated method stub @@ -491,7 +493,7 @@ public void cancel() { } @Test - public void testSourceObservableCallsUnsubscribe() throws InterruptedException { + public void testSourceFlowableCallsUnsubscribe() throws InterruptedException { final AtomicInteger subsCount = new AtomicInteger(0); final TestSubscriber ts = new TestSubscriber(); @@ -522,7 +524,7 @@ public void subscribe(Subscriber s) { } @Test - public void testSourceObservableRetry1() throws InterruptedException { + public void testSourceFlowableRetry1() throws InterruptedException { final AtomicInteger subsCount = new AtomicInteger(0); final TestSubscriber ts = new TestSubscriber(); @@ -541,7 +543,7 @@ public void subscribe(Subscriber s) { } @Test - public void testSourceObservableRetry0() throws InterruptedException { + public void testSourceFlowableRetry0() throws InterruptedException { final AtomicInteger subsCount = new AtomicInteger(0); final TestSubscriber ts = new TestSubscriber(); @@ -565,12 +567,14 @@ static final class SlowFlowable implements Publisher { final AtomicInteger active = new AtomicInteger(0); final AtomicInteger maxActive = new AtomicInteger(0); final AtomicInteger nextBeforeFailure; + final String context; private final int emitDelay; - SlowFlowable(int emitDelay, int countNext) { + SlowFlowable(int emitDelay, int countNext, String context) { this.emitDelay = emitDelay; this.nextBeforeFailure = new AtomicInteger(countNext); + this.context = context; } @Override @@ -592,7 +596,7 @@ public void cancel() { efforts.getAndIncrement(); active.getAndIncrement(); maxActive.set(Math.max(active.get(), maxActive.get())); - final Thread thread = new Thread() { + final Thread thread = new Thread(context) { @Override public void run() { long nr = 0; @@ -602,7 +606,9 @@ public void run() { if (nextBeforeFailure.getAndDecrement() > 0) { subscriber.onNext(nr++); } else { + active.decrementAndGet(); subscriber.onError(new RuntimeException("expected-failed")); + break; } } } catch (InterruptedException t) { @@ -663,7 +669,7 @@ public void testUnsubscribeAfterError() { Subscriber subscriber = TestHelper.mockSubscriber(); // Flowable that always fails after 100ms - SlowFlowable so = new SlowFlowable(100, 0); + SlowFlowable so = new SlowFlowable(100, 0, "testUnsubscribeAfterError"); Flowable f = Flowable.unsafeCreate(so).retry(5); AsyncSubscriber async = new AsyncSubscriber(subscriber); @@ -687,7 +693,7 @@ public void testTimeoutWithRetry() { Subscriber subscriber = TestHelper.mockSubscriber(); // Flowable that sends every 100ms (timeout fails instead) - SlowFlowable sf = new SlowFlowable(100, 10); + SlowFlowable sf = new SlowFlowable(100, 10, "testTimeoutWithRetry"); Flowable f = Flowable.unsafeCreate(sf).timeout(80, TimeUnit.MILLISECONDS).retry(5); AsyncSubscriber async = new AsyncSubscriber(subscriber); @@ -707,7 +713,7 @@ public void testTimeoutWithRetry() { @Test//(timeout = 15000) public void testRetryWithBackpressure() throws InterruptedException { final int NUM_LOOPS = 1; - for (int j = 0;j < NUM_LOOPS; j++) { + for (int j = 0; j < NUM_LOOPS; j++) { final int numRetries = Flowable.bufferSize() * 2; for (int i = 0; i < 400; i++) { Subscriber subscriber = TestHelper.mockSubscriber(); @@ -829,6 +835,7 @@ static StringBuilder sequenceFrequency(Iterable it) { return sb; } + @Test//(timeout = 3000) public void testIssue1900() throws InterruptedException { Subscriber subscriber = TestHelper.mockSubscriber(); @@ -850,7 +857,7 @@ public String apply(String t1) { return t1; } }) - .flatMap(new Function, Flowable>() { + .flatMap(new Function, Flowable>() { @Override public Flowable apply(GroupedFlowable t1) { return t1.take(1); @@ -869,6 +876,7 @@ public Flowable apply(GroupedFlowable t1) { inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } + @Test//(timeout = 3000) public void testIssue1900SourceNotSupportingBackpressure() { Subscriber subscriber = TestHelper.mockSubscriber(); @@ -894,7 +902,7 @@ public String apply(String t1) { return t1; } }) - .flatMap(new Function, Flowable>() { + .flatMap(new Function, Flowable>() { @Override public Flowable apply(GroupedFlowable t1) { return t1.take(1); @@ -997,9 +1005,8 @@ public boolean getAsBoolean() throws Exception { .assertResult(1, 1, 1, 1, 1); } - @Test - public void shouldDisposeInnerObservable() { + public void shouldDisposeInnerFlowable() { final PublishProcessor processor = PublishProcessor.create(); final Disposable disposable = Flowable.error(new RuntimeException("Leak")) .retryWhen(new Function, Flowable>() { @@ -1019,4 +1026,259 @@ public Flowable apply(Throwable ignore) throws Exception { disposable.dispose(); assertFalse(processor.hasSubscribers()); } + + @Test + public void noCancelPreviousRetry() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Flowable source = Flowable.defer(new Callable>() { + @Override + public Flowable call() throws Exception { + if (times.getAndIncrement() < 4) { + return Flowable.error(new TestException()); + } + return Flowable.just(1); + } + }) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retry(5) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRetryWhile() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Flowable source = Flowable.defer(new Callable>() { + @Override + public Flowable call() throws Exception { + if (times.getAndIncrement() < 4) { + return Flowable.error(new TestException()); + } + return Flowable.just(1); + } + }) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retry(5, Functions.alwaysTrue()) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRetryWhile2() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Flowable source = Flowable.defer(new Callable>() { + @Override + public Flowable call() throws Exception { + if (times.getAndIncrement() < 4) { + return Flowable.error(new TestException()); + } + return Flowable.just(1); + } + }) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retry(new BiPredicate() { + @Override + public boolean test(Integer a, Throwable b) throws Exception { + return a < 5; + } + }) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRetryUntil() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Flowable source = Flowable.defer(new Callable>() { + @Override + public Flowable call() throws Exception { + if (times.getAndIncrement() < 4) { + return Flowable.error(new TestException()); + } + return Flowable.just(1); + } + }) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retryUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return false; + } + }) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatWhen() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Flowable source = Flowable.defer(new Callable>() { + @Override + public Flowable call() throws Exception { + if (times.get() < 4) { + return Flowable.error(new TestException()); + } + return Flowable.just(1); + } + }).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retryWhen(new Function, Flowable>() { + @Override + public Flowable apply(Flowable e) throws Exception { + return e.takeWhile(new Predicate() { + @Override + public boolean test(Object v) throws Exception { + return times.getAndIncrement() < 4; + } + }); + } + }) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatWhen2() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Flowable source = Flowable.error(new TestException()) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retryWhen(new Function, Flowable>() { + @Override + public Flowable apply(Flowable e) throws Exception { + return e.takeWhile(new Predicate() { + @Override + public boolean test(Object v) throws Exception { + return times.getAndIncrement() < 4; + } + }); + } + }) + .test() + .assertResult(); + + assertEquals(0, counter.get()); + } + + @Test + public void repeatFloodNoSubscriptionError() { + List errors = TestHelper.trackPluginErrors(); + + final TestException error = new TestException(); + + try { + final PublishProcessor source = PublishProcessor.create(); + final PublishProcessor signaller = PublishProcessor.create(); + + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + TestSubscriber ts = source.take(1) + .map(new Function() { + @Override + public Integer apply(Integer v) throws Exception { + throw error; + } + }) + .retryWhen(new Function, Flowable>() { + @Override + public Flowable apply(Flowable v) + throws Exception { + return signaller; + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + source.onNext(1); + } + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + signaller.offer(1); + } + } + }; + + TestHelper.race(r1, r2); + + ts.dispose(); + } + + if (!errors.isEmpty()) { + for (Throwable e : errors) { + e.printStackTrace(); + } + fail(errors + ""); + } + } finally { + RxJavaPlugins.reset(); + } + } } 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 1f9f8c0736..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; @@ -70,6 +69,7 @@ public void testWithNothingToRetry() { inOrder.verify(subscriber).onComplete(); verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void testRetryTwice() { Flowable source = Flowable.unsafeCreate(new Publisher() { @@ -105,6 +105,7 @@ public void subscribe(Subscriber t1) { verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void testRetryTwiceAndGiveUp() { Flowable source = Flowable.unsafeCreate(new Publisher() { @@ -132,6 +133,7 @@ public void subscribe(Subscriber t1) { verify(subscriber, never()).onComplete(); } + @Test public void testRetryOnSpecificException() { Flowable source = Flowable.unsafeCreate(new Publisher() { @@ -166,6 +168,7 @@ public void subscribe(Subscriber t1) { inOrder.verify(subscriber).onComplete(); verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void testRetryOnSpecificExceptionAndNotOther() { final IOException ioe = new IOException(); @@ -226,7 +229,7 @@ public void testUnsubscribeAfterError() { Subscriber subscriber = TestHelper.mockSubscriber(); // Flowable that always fails after 100ms - FlowableRetryTest.SlowFlowable so = new FlowableRetryTest.SlowFlowable(100, 0); + FlowableRetryTest.SlowFlowable so = new FlowableRetryTest.SlowFlowable(100, 0, "testUnsubscribeAfterError"); Flowable f = Flowable .unsafeCreate(so) .retry(retry5); @@ -252,7 +255,7 @@ public void testTimeoutWithRetry() { Subscriber subscriber = TestHelper.mockSubscriber(); // Flowable that sends every 100ms (timeout fails instead) - FlowableRetryTest.SlowFlowable so = new FlowableRetryTest.SlowFlowable(100, 10); + FlowableRetryTest.SlowFlowable so = new FlowableRetryTest.SlowFlowable(100, 10, "testTimeoutWithRetry"); Flowable f = Flowable .unsafeCreate(so) .timeout(80, TimeUnit.MILLISECONDS) @@ -289,6 +292,7 @@ public Integer apply(Integer t1) { assertEquals(6, c.get()); assertEquals(Collections.singletonList(e), ts.errors()); } + @Test public void testJustAndRetry() throws Exception { final AtomicBoolean throwException = new AtomicBoolean(true); @@ -330,7 +334,7 @@ public void accept(Long t) { System.out.println(t); list.add(t); }}); - assertEquals(Arrays.asList(1L,1L,2L,3L), list); + assertEquals(Arrays.asList(1L, 1L, 2L, 3L), list); } @Test @@ -354,7 +358,7 @@ public void accept(Long t) { System.out.println(t); list.add(t); }}); - assertEquals(Arrays.asList(1L,1L,2L,3L), list); + assertEquals(Arrays.asList(1L, 1L, 2L, 3L), list); } @Test 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 12e354cda2..1ea39f76ce 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java @@ -13,7 +13,7 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; +import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; @@ -306,11 +306,20 @@ public void backpressureOverflow() { @Test public void backpressureOverflowWithOtherPublisher() { - BehaviorProcessor.createDefault(1) - .sample(Flowable.timer(1, TimeUnit.MILLISECONDS)) - .test(0L) - .awaitDone(5, TimeUnit.SECONDS) - .assertFailure(MissingBackpressureException.class); + PublishProcessor pp1 = PublishProcessor.create(); + PublishProcessor pp2 = PublishProcessor.create(); + + TestSubscriber ts = pp1 + .sample(pp2) + .test(0L); + + pp1.onNext(1); + pp2.onNext(2); + + ts.assertFailure(MissingBackpressureException.class); + + assertFalse(pp1.hasSubscribers()); + assertFalse(pp2.hasSubscribers()); } @Test @@ -455,5 +464,19 @@ public Flowable apply(Flowable f) return f.sample(1, TimeUnit.SECONDS); } }); + + TestHelper.checkDoubleOnSubscribeFlowable(new Function, Flowable>() { + @Override + public Flowable apply(Flowable f) + throws Exception { + return f.sample(PublishProcessor.create()); + } + }); + } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(PublishProcessor.create() + .sample(PublishProcessor.create())); } } 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 d12476cf4e..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; @@ -553,7 +552,7 @@ public Integer apply(Integer n1, Integer n2) throws Exception { @Test public void testScanWithSeedCompletesNormally() { - Flowable.just(1,2,3).scan(0, SUM) + Flowable.just(1, 2, 3).scan(0, SUM) .test() .assertValues(0, 1, 3, 6) .assertComplete(); @@ -562,7 +561,7 @@ public void testScanWithSeedCompletesNormally() { @Test public void testScanWithSeedWhenScanSeedProviderThrows() { final RuntimeException e = new RuntimeException(); - Flowable.just(1,2,3).scanWith(throwingCallable(e), + Flowable.just(1, 2, 3).scanWith(throwingCallable(e), SUM) .test() .assertError(e) @@ -631,7 +630,7 @@ public Integer apply(Integer n1, Integer n2) throws Exception { assertEquals(1, count.get()); } - private static BiFunction throwingBiFunction(final RuntimeException e) { + private static BiFunction throwingBiFunction(final RuntimeException e) { return new BiFunction() { @Override public Integer apply(Integer n1, Integer n2) throws Exception { 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 4ee2fad368..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; @@ -483,7 +482,6 @@ protected void subscribeActual(Subscriber s) { } } - @Test public void longSequenceEquals() { Flowable source = Flowable.range(1, Flowable.bufferSize() * 4).subscribeOn(Schedulers.computation()); 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 88cb84d40d..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.*; @@ -192,7 +191,6 @@ public void onNext(Integer t) { assertEquals(Arrays.asList(Long.MAX_VALUE), requests); } - @Test public void testSingleWithPredicateFlowable() { Flowable flowable = Flowable.just(1, 2) 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/FlowableSkipTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTest.java index 25d902310b..c2f8c2f40f 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTest.java @@ -167,7 +167,7 @@ public void testRequestOverflowDoesNotOccur() { ts.assertTerminated(); ts.assertComplete(); ts.assertNoErrors(); - assertEquals(Arrays.asList(6,7,8,9,10), ts.values()); + assertEquals(Arrays.asList(6, 7, 8, 9, 10), ts.values()); } @Test 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/FlowableSwitchIfEmptyTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmptyTest.java index 6c7cc67603..f429d8e172 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmptyTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmptyTest.java @@ -29,7 +29,6 @@ import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.*; - public class FlowableSwitchIfEmptyTest { @Test @@ -122,14 +121,13 @@ public void onNext(Long aLong) { } }).subscribe(); - assertTrue(bs.isCancelled()); // FIXME no longer assertable // assertTrue(sub.isUnsubscribed()); } @Test - public void testSwitchShouldTriggerUnsubscribe() { + public void testSwitchShouldNotTriggerUnsubscribe() { final BooleanSubscription bs = new BooleanSubscription(); Flowable.unsafeCreate(new Publisher() { @@ -139,7 +137,7 @@ public void subscribe(final Subscriber subscriber) { subscriber.onComplete(); } }).switchIfEmpty(Flowable.never()).subscribe(); - assertTrue(bs.isCancelled()); + assertFalse(bs.isCancelled()); } @Test @@ -156,6 +154,7 @@ public void testSwitchRequestAlternativeObservableWithBackpressure() { ts.request(1); ts.assertValueCount(3); } + @Test public void testBackpressureNoRequest() { TestSubscriber ts = new TestSubscriber(0L); @@ -167,7 +166,7 @@ public void testBackpressureNoRequest() { @Test public void testBackpressureOnFirstObservable() { TestSubscriber ts = new TestSubscriber(0L); - Flowable.just(1,2,3).switchIfEmpty(Flowable.just(4, 5, 6)).subscribe(ts); + Flowable.just(1, 2, 3).switchIfEmpty(Flowable.just(4, 5, 6)).subscribe(ts); ts.assertNotComplete(); ts.assertNoErrors(); ts.assertNoValues(); 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 1878d133a4..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; @@ -449,7 +449,6 @@ public void testBackpressure() { publishCompleted(o2, 50); publishCompleted(o3, 55); - final TestSubscriber testSubscriber = new TestSubscriber(); Flowable.switchOnNext(o).subscribe(new DefaultSubscriber() { @@ -583,7 +582,6 @@ public Flowable apply(Long t) { assertTrue(ts.valueCount() > 0); } - @Test(timeout = 10000) public void testSecondaryRequestsDontOverflow() throws InterruptedException { TestSubscriber ts = new TestSubscriber(0L); @@ -1204,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/FlowableTakeLastOneTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOneTest.java index 5da9f37c50..5a94904936 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOneTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOneTest.java @@ -91,7 +91,7 @@ public void testLastWithBackpressure() { public void testTakeLastZeroProcessesAllItemsButIgnoresThem() { final AtomicInteger upstreamCount = new AtomicInteger(); final int num = 10; - long count = Flowable.range(1,num).doOnNext(new Consumer() { + long count = Flowable.range(1, num).doOnNext(new Consumer() { @Override public void accept(Integer t) { 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 3277d5f831..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; @@ -241,7 +240,6 @@ public void onNext(Integer integer) { }); } - @Test public void testIgnoreRequest4() { // If `takeLast` does not ignore `request` properly, StackOverflowError will be thrown. @@ -293,7 +291,7 @@ public void onNext(Integer integer) { cancel(); } }); - assertEquals(1,count.get()); + assertEquals(1, count.get()); } @Test(timeout = 10000) 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 5510b534ea..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.*; @@ -451,7 +450,6 @@ public void accept(Integer v) { ts.assertComplete(); } - @Test public void takeNegative() { try { @@ -485,7 +483,6 @@ public Flowable apply(Flowable f) throws Exception { }); } - @Test public void badRequest() { TestHelper.assertBadRequestReported(Flowable.never().take(1)); 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 22e96421f0..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; @@ -47,6 +46,7 @@ public boolean test(Object v) { verify(subscriber, never()).onError(any(Throwable.class)); verify(subscriber).onComplete(); } + @Test public void takeAll() { Subscriber subscriber = TestHelper.mockSubscriber(); @@ -63,6 +63,7 @@ public boolean test(Integer v) { verify(subscriber, never()).onError(any(Throwable.class)); verify(subscriber).onComplete(); } + @Test public void takeFirst() { Subscriber subscriber = TestHelper.mockSubscriber(); @@ -79,6 +80,7 @@ public boolean test(Integer v) { verify(subscriber, never()).onError(any(Throwable.class)); verify(subscriber).onComplete(); } + @Test public void takeSome() { Subscriber subscriber = TestHelper.mockSubscriber(); @@ -97,6 +99,7 @@ public boolean test(Integer t1) { verify(subscriber, never()).onError(any(Throwable.class)); verify(subscriber).onComplete(); } + @Test public void functionThrows() { Subscriber subscriber = TestHelper.mockSubscriber(); @@ -115,6 +118,7 @@ public boolean test(Integer t1) { verify(subscriber).onError(any(TestException.class)); verify(subscriber, never()).onComplete(); } + @Test public void sourceThrows() { Subscriber subscriber = TestHelper.mockSubscriber(); @@ -134,6 +138,7 @@ public boolean test(Integer v) { verify(subscriber).onError(any(TestException.class)); verify(subscriber, never()).onComplete(); } + @Test public void backpressure() { TestSubscriber ts = new TestSubscriber(5L); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilTest.java index 702f4660e1..b254ecdf8d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilTest.java @@ -209,6 +209,7 @@ public void testUntilFires() { assertFalse("Until still has observers", until.hasSubscribers()); assertFalse("TestSubscriber is unsubscribed", ts.isCancelled()); } + @Test public void testMainCompletes() { PublishProcessor source = PublishProcessor.create(); @@ -232,6 +233,7 @@ public void testMainCompletes() { assertFalse("Until still has observers", until.hasSubscribers()); assertFalse("TestSubscriber is unsubscribed", ts.isCancelled()); } + @Test public void testDownstreamUnsubscribes() { PublishProcessor source = PublishProcessor.create(); 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 f9dc9c01f1..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; @@ -153,7 +152,6 @@ public void testThrottle() { inOrder.verifyNoMoreInteractions(); } - @Test public void throttleFirstDefaultScheduler() { Flowable.just(1).throttleFirst(100, TimeUnit.MILLISECONDS) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatestTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatestTest.java index 2b07d178d9..5612307c28 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatestTest.java @@ -130,7 +130,6 @@ public void normal() { ts.assertResult(1, 3, 5, 6); } - @Test public void normalEmitLast() { TestScheduler sch = new TestScheduler(); 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 ded3661e95..5f6aa08fa6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java @@ -13,8 +13,8 @@ package io.reactivex.internal.operators.flowable; +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; @@ -82,26 +82,24 @@ public void shouldNotTimeoutIfSecondOnNextWithinTimeout() { @Test public void shouldTimeoutIfOnNextNotWithinTimeout() { - Subscriber subscriber = TestHelper.mockSubscriber(); - TestSubscriber ts = new TestSubscriber(subscriber); + TestSubscriber subscriber = new TestSubscriber(); - withTimeout.subscribe(ts); + withTimeout.subscribe(subscriber); testScheduler.advanceTimeBy(TIMEOUT + 1, TimeUnit.SECONDS); - verify(subscriber).onError(any(TimeoutException.class)); - ts.dispose(); + subscriber.assertFailureAndMessage(TimeoutException.class, timeoutMessage(TIMEOUT, TIME_UNIT)); } @Test public void shouldTimeoutIfSecondOnNextNotWithinTimeout() { - Subscriber subscriber = TestHelper.mockSubscriber(); + TestSubscriber subscriber = new TestSubscriber(); TestSubscriber ts = new TestSubscriber(subscriber); withTimeout.subscribe(subscriber); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); - verify(subscriber).onNext("One"); + subscriber.assertValue("One"); testScheduler.advanceTimeBy(TIMEOUT + 1, TimeUnit.SECONDS); - verify(subscriber).onError(any(TimeoutException.class)); + subscriber.assertFailureAndMessage(TimeoutException.class, timeoutMessage(TIMEOUT, TIME_UNIT), "One"); ts.dispose(); } @@ -235,8 +233,7 @@ public void shouldTimeoutIfSynchronizedFlowableEmitFirstOnNextNotWithinTimeout() final CountDownLatch exit = new CountDownLatch(1); final CountDownLatch timeoutSetuped = new CountDownLatch(1); - final Subscriber subscriber = TestHelper.mockSubscriber(); - final TestSubscriber ts = new TestSubscriber(subscriber); + final TestSubscriber subscriber = new TestSubscriber(); new Thread(new Runnable() { @@ -258,16 +255,14 @@ public void subscribe(Subscriber subscriber) { } }).timeout(1, TimeUnit.SECONDS, testScheduler) - .subscribe(ts); + .subscribe(subscriber); } }).start(); timeoutSetuped.await(); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); - InOrder inOrder = inOrder(subscriber); - inOrder.verify(subscriber, times(1)).onError(isA(TimeoutException.class)); - inOrder.verifyNoMoreInteractions(); + subscriber.assertFailureAndMessage(TimeoutException.class, timeoutMessage(1, TimeUnit.SECONDS)); exit.countDown(); // exit the thread } @@ -287,15 +282,12 @@ public void subscribe(Subscriber subscriber) { TestScheduler testScheduler = new TestScheduler(); Flowable observableWithTimeout = never.timeout(1000, TimeUnit.MILLISECONDS, testScheduler); - Subscriber subscriber = TestHelper.mockSubscriber(); - TestSubscriber ts = new TestSubscriber(subscriber); - observableWithTimeout.subscribe(ts); + TestSubscriber subscriber = new TestSubscriber(); + observableWithTimeout.subscribe(subscriber); testScheduler.advanceTimeBy(2000, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(subscriber); - inOrder.verify(subscriber).onError(isA(TimeoutException.class)); - inOrder.verifyNoMoreInteractions(); + subscriber.assertFailureAndMessage(TimeoutException.class, timeoutMessage(1000, TimeUnit.MILLISECONDS)); verify(s, times(1)).cancel(); } @@ -477,7 +469,6 @@ protected void subscribeActual(Subscriber subscriber) { } } - @Test public void timedTake() { PublishProcessor pp = PublishProcessor.create(); @@ -549,11 +540,13 @@ public void run() { if (ts.valueCount() != 0) { if (ts.errorCount() != 0) { ts.assertFailure(TimeoutException.class, 1); + ts.assertErrorMessage(timeoutMessage(1, TimeUnit.SECONDS)); } else { ts.assertValuesOnly(1); } } else { ts.assertFailure(TimeoutException.class); + ts.assertErrorMessage(timeoutMessage(1, TimeUnit.SECONDS)); } } } 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 21009d0da7..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; @@ -86,6 +85,7 @@ public void testTimerPeriodically() { ts.assertNotComplete(); ts.assertNoErrors(); } + @Test public void testInterval() { Flowable w = Flowable.interval(1, TimeUnit.SECONDS, scheduler); @@ -226,6 +226,7 @@ public void testWithMultipleStaggeredSubscribersAndPublish() { ts2.assertNoErrors(); ts2.assertNotComplete(); } + @Test public void testOnceObserverThrows() { Flowable source = Flowable.timer(100, TimeUnit.MILLISECONDS, scheduler); @@ -254,6 +255,7 @@ public void onComplete() { verify(subscriber, never()).onNext(anyLong()); verify(subscriber, never()).onComplete(); } + @Test public void testPeriodicObserverThrows() { Flowable source = Flowable.interval(100, 100, TimeUnit.MILLISECONDS, scheduler); 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 20a0534285..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.*; @@ -101,6 +100,7 @@ public void testListWithBlockingFirstFlowable() { List actual = f.toList().toFlowable().blockingFirst(); Assert.assertEquals(Arrays.asList("one", "two", "three"), actual); } + @Test public void testBackpressureHonoredFlowable() { Flowable> w = Flowable.just(1, 2, 3, 4, 5).toList().toFlowable(); @@ -124,6 +124,7 @@ public void testBackpressureHonoredFlowable() { ts.assertNoErrors(); ts.assertComplete(); } + @Test(timeout = 2000) @Ignore("PublishProcessor no longer emits without requests so this test fails due to the race of onComplete and request") public void testAsyncRequestedFlowable() { @@ -230,6 +231,7 @@ public void testListWithBlockingFirst() { List actual = f.toList().blockingGet(); Assert.assertEquals(Arrays.asList("one", "two", "three"), actual); } + @Test @Ignore("Single doesn't do backpressure") public void testBackpressureHonored() { @@ -254,6 +256,7 @@ public void testBackpressureHonored() { to.assertNoErrors(); to.assertComplete(); } + @Test(timeout = 2000) @Ignore("PublishProcessor no longer emits without requests so this test fails due to the race of onComplete and request") public void testAsyncRequested() { diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMapTest.java index f8914e7182..296f68eef6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMapTest.java @@ -224,7 +224,6 @@ public String apply(String v) { verify(objectSubscriber, times(1)).onError(any(Throwable.class)); } - @Test public void testToMap() { Flowable source = Flowable.just("a", "bb", "ccc", "dddd"); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMultimapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMultimapTest.java index 1546815355..bb77dab822 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMultimapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMultimapTest.java @@ -296,7 +296,6 @@ public Map> call() { verify(objectSubscriber, never()).onComplete(); } - @Test public void testToMultimap() { Flowable source = Flowable.just("a", "b", "cc", "dd"); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToSortedListTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToSortedListTest.java index bea7ef749a..173da4830d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToSortedListTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToSortedListTest.java @@ -69,6 +69,7 @@ public void testWithFollowingFirstFlowable() { Flowable f = Flowable.just(1, 3, 2, 5, 4); assertEquals(Arrays.asList(1, 2, 3, 4, 5), f.toSortedList().toFlowable().blockingFirst()); } + @Test public void testBackpressureHonoredFlowable() { Flowable> w = Flowable.just(1, 3, 2, 5, 4).toSortedList().toFlowable(); @@ -202,6 +203,7 @@ public void testWithFollowingFirst() { Flowable f = Flowable.just(1, 3, 2, 5, 4); assertEquals(Arrays.asList(1, 2, 3, 4, 5), f.toSortedList().blockingGet()); } + @Test @Ignore("Single doesn't do backpressure") public void testBackpressureHonored() { 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 3ed444424f..36112023bb 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java @@ -47,7 +47,10 @@ public void subscribe(Subscriber t1) { t1.onSubscribe(subscription); t1.onNext(1); t1.onNext(2); - t1.onComplete(); + // observeOn will prevent canceling the upstream upon its termination now + // this call is racing for that state in this test + // not doing it will make sure the unsubscribeOn always gets through + // t1.onComplete(); } }); @@ -70,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(); @@ -93,7 +96,10 @@ public void subscribe(Subscriber t1) { t1.onSubscribe(subscription); t1.onNext(1); t1.onNext(2); - t1.onComplete(); + // observeOn will prevent canceling the upstream upon its termination now + // this call is racing for that state in this test + // not doing it will make sure the unsubscribeOn always gets through + // t1.onComplete(); } }); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUsingTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUsingTest.java index b67437e7a9..cfc8ab20b1 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUsingTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUsingTest.java @@ -331,8 +331,6 @@ public Flowable apply(Resource resource) { } - - @Test public void testUsingDisposesEagerlyBeforeError() { final List events = new ArrayList(); 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 a6469281c4..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.*; @@ -75,7 +74,6 @@ public void onComplete() { } source.onComplete(); - verify(subscriber, never()).onError(any(Throwable.class)); assertEquals(n / 3, values.size()); @@ -326,6 +324,7 @@ public Flowable call() { ts.assertNoErrors(); ts.assertValueCount(1); } + @Test public void testMainUnsubscribedOnBoundaryCompletion() { PublishProcessor source = PublishProcessor.create(); @@ -345,7 +344,6 @@ public Flowable call() { boundary.onComplete(); - assertFalse(source.hasSubscribers()); assertFalse(boundary.hasSubscribers()); @@ -373,7 +371,6 @@ public Flowable call() { ts.dispose(); - assertTrue(source.hasSubscribers()); assertFalse(boundary.hasSubscribers()); @@ -386,6 +383,7 @@ public Flowable call() { ts.assertNoErrors(); ts.assertValueCount(1); } + @Test public void testInnerBackpressure() { Flowable source = Flowable.range(1, 10); @@ -771,7 +769,6 @@ public Flowable apply( ts.assertResult(1); } - @Test public void mainAndBoundaryBothError() { List errors = TestHelper.trackPluginErrors(); 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 aeb5381da7..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.*; @@ -211,6 +210,7 @@ public void testBackpressureOuter() { public void onStart() { request(1); } + @Override public void onNext(Flowable t) { t.subscribe(new DefaultSubscriber() { @@ -218,20 +218,24 @@ public void onNext(Flowable t) { public void onNext(Integer t) { list.add(t); } + @Override public void onError(Throwable e) { subscriber.onError(e); } + @Override public void onComplete() { subscriber.onComplete(); } }); } + @Override public void onError(Throwable e) { subscriber.onError(e); } + @Override public void onComplete() { subscriber.onComplete(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithStartEndFlowableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithStartEndFlowableTest.java index c1d825afed..1d27381129 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithStartEndFlowableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithStartEndFlowableTest.java @@ -17,12 +17,13 @@ import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.*; import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.exceptions.TestException; +import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; @@ -254,8 +255,8 @@ public Flowable apply(Integer t) { ts.dispose(); - // FIXME subject has subscribers because of the open window - assertTrue(open.hasSubscribers()); + // Disposing the outer sequence stops the opening of new windows + assertFalse(open.hasSubscribers()); // FIXME subject has subscribers because of the open window assertTrue(close.hasSubscribers()); } @@ -430,4 +431,58 @@ protected void subscribeActual( RxJavaPlugins.reset(); } } + + static Flowable flowableDisposed(final AtomicBoolean ref) { + return Flowable.just(1).concatWith(Flowable.never()) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + ref.set(true); + } + }); + } + + @Test + public void mainAndBoundaryDisposeOnNoWindows() { + AtomicBoolean mainDisposed = new AtomicBoolean(); + AtomicBoolean openDisposed = new AtomicBoolean(); + final AtomicBoolean closeDisposed = new AtomicBoolean(); + + flowableDisposed(mainDisposed) + .window(flowableDisposed(openDisposed), new Function>() { + @Override + public Flowable apply(Integer v) throws Exception { + return flowableDisposed(closeDisposed); + } + }) + .test() + .assertSubscribed() + .assertNoErrors() + .assertNotComplete() + .dispose(); + + assertTrue(mainDisposed.get()); + assertTrue(openDisposed.get()); + assertTrue(closeDisposed.get()); + } + + @Test + @SuppressWarnings("unchecked") + public void mainWindowMissingBackpressure() { + PublishProcessor source = PublishProcessor.create(); + PublishProcessor boundary = PublishProcessor.create(); + + TestSubscriber> ts = source.window(boundary, Functions.justFunction(Flowable.never())) + .test(0L) + ; + + ts.assertEmpty(); + + boundary.onNext(1); + + ts.assertFailure(MissingBackpressureException.class); + + assertFalse(source.hasSubscribers()); + assertFalse(boundary.hasSubscribers()); + } } 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 296c973963..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.*; @@ -32,7 +32,6 @@ import io.reactivex.schedulers.*; import io.reactivex.subscribers.*; - public class FlowableWindowWithTimeTest { private TestScheduler scheduler; @@ -65,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 @@ -158,6 +159,7 @@ public void onNext(T args) { } }; } + @Test public void testExactWindowSize() { Flowable> source = Flowable.range(1, 10) @@ -222,7 +224,6 @@ public void accept(Integer pv) { Assert.assertTrue(ts.valueCount() != 0); } - @Test public void timespanTimeskipCustomSchedulerBufferSize() { Flowable.range(1, 10) @@ -811,6 +812,7 @@ public void periodicWindowCompletionRestartTimerBoundedSomeData() { .assertNoErrors() .assertNotComplete(); } + @Test public void countRestartsOnTimeTick() { TestScheduler scheduler = new TestScheduler(); @@ -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 e87ff9fe50..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.*; @@ -134,7 +133,6 @@ public void testEmptyOther() { assertFalse(other.hasSubscribers()); } - @Test public void testUnsubscription() { PublishProcessor source = PublishProcessor.create(); @@ -189,6 +187,7 @@ public void testSourceThrows() { assertFalse(source.hasSubscribers()); assertFalse(other.hasSubscribers()); } + @Test public void testOtherThrows() { PublishProcessor source = PublishProcessor.create(); 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 b02fa664fe..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.*; @@ -1224,6 +1223,7 @@ public Integer apply(Integer i1, Integer i2) { } assertEquals(expected, zip2.toList().blockingGet()); } + @Test public void testUnboundedDownstreamOverrequesting() { Flowable source = Flowable.range(1, 2).zipWith(Flowable.range(1, 2), new BiFunction() { @@ -1247,6 +1247,7 @@ public void onNext(Integer t) { ts.assertTerminated(); ts.assertValues(11, 22); } + @Test(timeout = 10000) public void testZipRace() { long startTime = System.currentTimeMillis(); @@ -1570,6 +1571,7 @@ public Object apply(Integer a, Integer b, Integer c, Integer d, Integer e, Integ .test() .assertResult("12345678"); } + @Test public void zip9() { Flowable.zip(Flowable.just(1), @@ -1589,7 +1591,6 @@ public Object apply(Integer a, Integer b, Integer c, Integer d, Integer e, Integ .assertResult("123456789"); } - @Test public void zipArrayMany() { @SuppressWarnings("unchecked") @@ -1893,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/flowable/NotificationLiteTest.java b/src/test/java/io/reactivex/internal/operators/flowable/NotificationLiteTest.java index a03d91385f..1f29136e00 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/NotificationLiteTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/NotificationLiteTest.java @@ -23,7 +23,6 @@ import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.internal.util.NotificationLite; - public class NotificationLiteTest { @Test diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeAmbTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeAmbTest.java index a50c685233..a701a279ab 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeAmbTest.java @@ -16,15 +16,21 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import io.reactivex.*; import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.*; public class MaybeAmbTest { @@ -129,4 +135,122 @@ protected void subscribeActual( to.assertResult(1); } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerSuccessDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Maybe.ambArray( + Maybe.just(1) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Maybe.never() + ) + .subscribe(new Consumer() { + @Override + public void accept(Object v) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerErrorDispose() throws Exception { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Maybe.ambArray( + Maybe.error(ex) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Maybe.never() + ) + .subscribe(Functions.emptyConsumer(), new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerCompleteDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Maybe.ambArray( + Maybe.empty() + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Maybe.never() + ) + .subscribe(Functions.emptyConsumer(), Functions.emptyConsumer(), new Action() { + @Override + public void run() throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @Test + public void nullSourceSuccessRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List errors = TestHelper.trackPluginErrors(); + + try { + + final Subject ps = ReplaySubject.create(); + ps.onNext(1); + + @SuppressWarnings("unchecked") + final Maybe source = Maybe.ambArray(ps.singleElement(), + Maybe.never(), Maybe.never(), null); + + Runnable r1 = new Runnable() { + @Override + public void run() { + source.test(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ps.onComplete(); + } + }; + + TestHelper.race(r1, r2); + + if (!errors.isEmpty()) { + TestHelper.assertError(errors, 0, NullPointerException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeCacheTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeCacheTest.java index 57c856b472..bae05a46b4 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeCacheTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeCacheTest.java @@ -52,7 +52,6 @@ public void offlineError() { .assertFailure(TestException.class); } - @Test public void offlineComplete() { Maybe source = Maybe.empty().cache(); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayTest.java index 29eac9a2a3..fb14e27a19 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayTest.java @@ -151,7 +151,6 @@ protected void subscribeActual(MaybeObserver observer) { o[0].onError(new TestException()); - TestHelper.assertUndeliverable(errors, 0, TestException.class); } finally { RxJavaPlugins.reset(); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeContainsTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeContainsTest.java index 588de845a8..60f319f873 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeContainsTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeContainsTest.java @@ -59,7 +59,6 @@ public void dispose() { assertFalse(pp.hasSubscribers()); } - @Test public void isDisposed() { PublishProcessor pp = PublishProcessor.create(); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherTest.java index 40cb873836..4719ac8977 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherTest.java @@ -65,7 +65,6 @@ public void justWithOnComplete() { to.assertResult(1); } - @Test public void justWithOnError() { PublishProcessor pp = PublishProcessor.create(); @@ -102,7 +101,6 @@ public void emptyWithOnNext() { to.assertResult(); } - @Test public void emptyWithOnComplete() { PublishProcessor pp = PublishProcessor.create(); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminateTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminateTest.java new file mode 100644 index 0000000000..0e90e57731 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminateTest.java @@ -0,0 +1,122 @@ +/** + * 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.maybe; + +import io.reactivex.Maybe; +import io.reactivex.TestHelper; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.observers.TestObserver; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertTrue; + +public class MaybeDoOnTerminateTest { + + @Test(expected = NullPointerException.class) + public void doOnTerminate() { + Maybe.just(1).doOnTerminate(null); + } + + @Test + public void doOnTerminateSuccess() { + final AtomicBoolean atomicBoolean = new AtomicBoolean(); + Maybe.just(1).doOnTerminate(new Action() { + @Override + public void run() { + atomicBoolean.set(true); + } + }) + .test() + .assertResult(1); + + assertTrue(atomicBoolean.get()); + } + + @Test + public void doOnTerminateError() { + final AtomicBoolean atomicBoolean = new AtomicBoolean(); + Maybe.error(new TestException()).doOnTerminate(new Action() { + @Override + public void run() { + atomicBoolean.set(true); + } + }) + .test() + .assertFailure(TestException.class); + + assertTrue(atomicBoolean.get()); + } + + @Test + public void doOnTerminateComplete() { + final AtomicBoolean atomicBoolean = new AtomicBoolean(); + Maybe.empty().doOnTerminate(new Action() { + @Override + public void run() { + atomicBoolean.set(true); + } + }) + .test() + .assertResult(); + + assertTrue(atomicBoolean.get()); + } + + @Test + public void doOnTerminateSuccessCrash() { + Maybe.just(1).doOnTerminate(new Action() { + @Override + public void run() { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doOnTerminateErrorCrash() { + TestObserver to = Maybe.error(new TestException("Outer")) + .doOnTerminate(new Action() { + @Override + public void run() { + throw new TestException("Inner"); + } + }) + .test() + .assertFailure(CompositeException.class); + + List errors = TestHelper.compositeList(to.errors().get(0)); + TestHelper.assertError(errors, 0, TestException.class, "Outer"); + TestHelper.assertError(errors, 1, TestException.class, "Inner"); + } + + @Test + public void doOnTerminateCompleteCrash() { + Maybe.empty() + .doOnTerminate(new Action() { + @Override + public void run() { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } +} 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/maybe/MaybeFromCompletableTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCompletableTest.java index 1b511dd515..3086eb6c57 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCompletableTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCompletableTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.maybe; - import io.reactivex.*; import io.reactivex.functions.Function; import io.reactivex.internal.fuseable.HasUpstreamCompletableSource; diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeIsEmptyTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeIsEmptyTest.java index 3efe9dbec3..3602d44aa0 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeIsEmptyTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeIsEmptyTest.java @@ -54,7 +54,6 @@ public void fusedBackToMaybe() { .toMaybe() instanceof MaybeIsEmpty); } - @Test public void normalToMaybe() { Maybe.just(1) diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeMaterializeTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeMaterializeTest.java new file mode 100644 index 0000000000..f429ecddf2 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeMaterializeTest.java @@ -0,0 +1,67 @@ +/** + * 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.maybe; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.subjects.MaybeSubject; + +public class MaybeMaterializeTest { + + @Test + @SuppressWarnings("unchecked") + public void success() { + Maybe.just(1) + .materialize() + .test() + .assertResult(Notification.createOnNext(1)); + } + + @Test + @SuppressWarnings("unchecked") + public void error() { + TestException ex = new TestException(); + Maybe.error(ex) + .materialize() + .test() + .assertResult(Notification.createOnError(ex)); + } + + @Test + @SuppressWarnings("unchecked") + public void empty() { + Maybe.empty() + .materialize() + .test() + .assertResult(Notification.createOnComplete()); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeMaybeToSingle(new Function, SingleSource>>() { + @Override + public SingleSource> apply(Maybe v) throws Exception { + return v.materialize(); + } + }); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(MaybeSubject.create().materialize()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingleTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingleTest.java index bacd1c1870..2c242fc1a5 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingleTest.java @@ -61,7 +61,6 @@ public void dispose() { assertFalse(pp.hasSubscribers()); } - @Test public void isDisposed() { PublishProcessor pp = PublishProcessor.create(); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptyTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptyTest.java index d2e53a7225..142943ba67 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptyTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptyTest.java @@ -76,7 +76,6 @@ public void dispose() { assertFalse(pp.hasSubscribers()); } - @Test public void isDisposed() { PublishProcessor pp = PublishProcessor.create(); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeUsingTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeUsingTest.java index c78a7cf5d7..d007667097 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeUsingTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeUsingTest.java @@ -346,7 +346,6 @@ public void accept(Object d) throws Exception { .assertFailure(TestException.class); } - @Test public void emptyDisposerCrashes() { Maybe.using(new Callable() { diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeZipArrayTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeZipArrayTest.java index 7fb24e1e27..8d07b6be3a 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeZipArrayTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeZipArrayTest.java @@ -36,7 +36,6 @@ public Object apply(Object a, Object b) throws Exception { } }; - final Function3 addString3 = new Function3() { @Override public Object apply(Object a, Object b, Object c) throws Exception { @@ -150,6 +149,7 @@ public void run() { } } } + @SuppressWarnings("unchecked") @Test(expected = NullPointerException.class) public void zipArrayOneIsNull() { diff --git a/src/test/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservableTest.java b/src/test/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservableTest.java index e75c8538ca..8493f9a15e 100644 --- a/src/test/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservableTest.java +++ b/src/test/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservableTest.java @@ -63,7 +63,6 @@ public void cancelOther() { assertFalse(ps.hasObservers()); } - @Test public void errorMain() { CompletableSubject cs = CompletableSubject.create(); diff --git a/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybeTest.java index d080928047..58188a34eb 100644 --- a/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybeTest.java @@ -399,7 +399,6 @@ public MaybeSource apply(Integer v) } } - @Test public void innerErrorAfterTermination() { List errors = TestHelper.trackPluginErrors(); diff --git a/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingleTest.java index 3c6c832b76..803e9bf013 100644 --- a/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingleTest.java @@ -347,7 +347,6 @@ public SingleSource apply(Integer v) } } - @Test public void innerErrorAfterTermination() { List errors = TestHelper.trackPluginErrors(); diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybeTest.java index 3faa98caa5..7830b885da 100644 --- a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybeTest.java @@ -375,7 +375,6 @@ public MaybeSource apply(Integer v) } } - @Test public void innerErrorAfterTermination() { List errors = TestHelper.trackPluginErrors(); diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingleTest.java index 03c38e54de..da35b10df1 100644 --- a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingleTest.java @@ -344,7 +344,6 @@ public SingleSource apply(Integer v) } } - @Test public void innerErrorAfterTermination() { List errors = TestHelper.trackPluginErrors(); 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 d37fc5fb74..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; @@ -124,6 +123,7 @@ public boolean test(Integer i) { assertFalse(allOdd.blockingFirst()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstreamObservable() { Observable source = Observable.just(1) @@ -143,7 +143,6 @@ public Observable apply(Boolean t1) { assertEquals((Object)2, source.blockingFirst()); } - @Test public void testPredicateThrowsExceptionAndValueInCauseMessageObservable() { TestObserver to = new TestObserver(); @@ -166,7 +165,6 @@ public boolean test(String v) { // assertTrue(ex.getCause().getMessage().contains("Boo!")); } - @Test public void testAll() { Observable obs = Observable.just("one", "two", "six"); @@ -256,6 +254,7 @@ public boolean test(Integer i) { assertFalse(allOdd.blockingGet()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstream() { Observable source = Observable.just(1) @@ -275,7 +274,6 @@ public Observable apply(Boolean t1) { assertEquals((Object)2, source.blockingFirst()); } - @Test public void testPredicateThrowsExceptionAndValueInCauseMessage() { TestObserver to = new TestObserver(); 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 ee4d58adf5..a76b8f22e6 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableAmbTest.java @@ -18,8 +18,8 @@ import java.io.IOException; import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; import org.junit.*; import org.mockito.InOrder; @@ -29,7 +29,8 @@ import io.reactivex.Observer; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; -import io.reactivex.functions.Consumer; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.*; @@ -263,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() { @@ -383,4 +401,84 @@ public void ambArrayOrder() { Observable error = Observable.error(new RuntimeException()); Observable.ambArray(Observable.just(1), error).test().assertValue(1).assertComplete(); } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerSuccessDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Observable.ambArray( + Observable.just(1) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Observable.never() + ) + .subscribe(new Consumer() { + @Override + public void accept(Object v) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerErrorDispose() throws Exception { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Observable.ambArray( + Observable.error(ex) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Observable.never() + ) + .subscribe(Functions.emptyConsumer(), new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerCompleteDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Observable.ambArray( + Observable.empty() + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Observable.never() + ) + .subscribe(Functions.emptyConsumer(), Functions.emptyConsumer(), new Action() { + @Override + public void run() throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + } 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 150a13580f..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; @@ -231,6 +230,7 @@ public boolean test(Integer i) { assertTrue(anyEven.blockingFirst()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstreamObservable() { Observable source = Observable.just(1).isEmpty().toObservable() @@ -452,6 +452,7 @@ public boolean test(Integer i) { assertTrue(anyEven.blockingGet()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstream() { Observable source = Observable.just(1).isEmpty() 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 9ce8103e77..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; @@ -497,6 +496,7 @@ public void bufferWithBOBoundaryThrows() { verify(o, never()).onComplete(); verify(o, never()).onNext(any()); } + @Test(timeout = 2000) public void bufferWithSizeTake1() { Observable source = Observable.just(1).repeat(); @@ -526,6 +526,7 @@ public void bufferWithSizeSkipTake1() { verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test(timeout = 2000) public void bufferWithTimeTake1() { Observable source = Observable.interval(40, 40, TimeUnit.MILLISECONDS, scheduler); @@ -542,6 +543,7 @@ public void bufferWithTimeTake1() { verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test(timeout = 2000) public void bufferWithTimeSkipTake2() { Observable source = Observable.interval(40, 40, TimeUnit.MILLISECONDS, scheduler); @@ -560,6 +562,7 @@ public void bufferWithTimeSkipTake2() { inOrder.verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test(timeout = 2000) public void bufferWithBoundaryTake2() { Observable boundary = Observable.interval(60, 60, TimeUnit.MILLISECONDS, scheduler); @@ -614,6 +617,7 @@ public void accept(List pv) { inOrder.verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test public void bufferWithSizeThrows() { PublishSubject source = PublishSubject.create(); @@ -683,6 +687,7 @@ public void bufferWithTimeAndSize() { inOrder.verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test public void bufferWithStartEndStartThrows() { PublishSubject start = PublishSubject.create(); @@ -711,6 +716,7 @@ public Observable apply(Integer t1) { verify(o, never()).onComplete(); verify(o).onError(any(TestException.class)); } + @Test public void bufferWithStartEndEndFunctionThrows() { PublishSubject start = PublishSubject.create(); @@ -738,6 +744,7 @@ public Observable apply(Integer t1) { verify(o, never()).onComplete(); verify(o).onError(any(TestException.class)); } + @Test public void bufferWithStartEndEndThrows() { PublishSubject start = PublishSubject.create(); @@ -776,11 +783,13 @@ public void testBufferWithTimeDoesntUnsubscribeDownstream() throws InterruptedEx public void onNext(Object t) { o.onNext(t); } + @Override public void onError(Throwable e) { o.onError(e); cdl.countDown(); } + @Override public void onComplete() { o.onComplete(); @@ -1547,7 +1556,7 @@ public Integer apply(Integer integer, Long aLong) { return integer; } }) - .buffer(Observable.interval(0,200, TimeUnit.MILLISECONDS), + .buffer(Observable.interval(0, 200, TimeUnit.MILLISECONDS), new Function>() { @Override public Observable apply(Long a) { @@ -1570,7 +1579,7 @@ public Integer apply(Integer integer, Long aLong) { return integer; } }) - .buffer(Observable.interval(0,100, TimeUnit.MILLISECONDS), + .buffer(Observable.interval(0, 100, TimeUnit.MILLISECONDS), new Function>() { @Override public Observable apply(Long a) { @@ -2126,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/ObservableCacheTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableCacheTest.java index c664c2591c..989206f156 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableCacheTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableCacheTest.java @@ -35,7 +35,7 @@ public class ObservableCacheTest { @Test public void testColdReplayNoBackpressure() { - ObservableCache source = (ObservableCache)ObservableCache.from(Observable.range(0, 1000)); + ObservableCache source = new ObservableCache(Observable.range(0, 1000), 16); assertFalse("Source is connected!", source.isConnected()); @@ -113,14 +113,14 @@ public void testUnsubscribeSource() throws Exception { o.subscribe(); o.subscribe(); o.subscribe(); - verify(unsubscribe, times(1)).run(); + verify(unsubscribe, never()).run(); } @Test public void testTake() { TestObserver to = new TestObserver(); - ObservableCache cached = (ObservableCache)ObservableCache.from(Observable.range(1, 100)); + ObservableCache cached = new ObservableCache(Observable.range(1, 1000), 16); cached.take(10).subscribe(to); to.assertNoErrors(); @@ -136,7 +136,7 @@ public void testAsync() { for (int i = 0; i < 100; i++) { TestObserver to1 = new TestObserver(); - ObservableCache cached = (ObservableCache)ObservableCache.from(source); + ObservableCache cached = new ObservableCache(source, 16); cached.observeOn(Schedulers.computation()).subscribe(to1); @@ -154,12 +154,13 @@ public void testAsync() { assertEquals(10000, to2.values().size()); } } + @Test public void testAsyncComeAndGo() { Observable source = Observable.interval(1, 1, TimeUnit.MILLISECONDS) .take(1000) .subscribeOn(Schedulers.io()); - ObservableCache cached = (ObservableCache)ObservableCache.from(source); + ObservableCache cached = new ObservableCache(source, 16); Observable output = cached.observeOn(Schedulers.computation()); @@ -220,7 +221,6 @@ public void testValuesAndThenError() { .concatWith(Observable.error(new TestException())) .cache(); - TestObserver to = new TestObserver(); source.subscribe(to); @@ -351,4 +351,23 @@ public void run() { .assertSubscribed().assertValueCount(500).assertComplete().assertNoErrors(); } } + + @Test + public void cancelledUpFront() { + final AtomicInteger call = new AtomicInteger(); + Observable f = Observable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + return call.incrementAndGet(); + } + }).concatWith(Observable.never()) + .cache(); + + f.test().assertValuesOnly(1); + + f.test(true) + .assertEmpty(); + + assertEquals(1, call.get()); + } } 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/ObservableConcatMapCompletableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapCompletableTest.java index 63789a756d..ab34d4e03e 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapCompletableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapCompletableTest.java @@ -155,7 +155,6 @@ public void mapperThrows() { .assertFailure(TestException.class); } - @Test public void fusedPollThrows() { Observable.just(1) diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapEagerTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapEagerTest.java index 0f6920ecb3..17e418bab7 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapEagerTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapEagerTest.java @@ -623,7 +623,6 @@ public Observable apply(Integer t) { to.assertValue(null); } - @Test @Ignore("Observable doesn't do backpressure") public void testMaxConcurrent5() { @@ -1142,4 +1141,37 @@ public void arrayDelayErrorMaxConcurrencyErrorDelayed() { to.assertFailure(TestException.class, 1, 2); } + + @Test + public void cancelActive() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + TestObserver to = Observable + .concatEager(Observable.just(ps1, ps2)) + .test(); + + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + + to.dispose(); + + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + } + + @Test + public void cancelNoInnerYet() { + PublishSubject> ps1 = PublishSubject.create(); + + TestObserver to = Observable + .concatEager(ps1) + .test(); + + assertTrue(ps1.hasObservers()); + + to.dispose(); + + assertFalse(ps1.hasObservers()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapTest.java index 5c2681a79d..4940d6c857 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapTest.java @@ -13,17 +13,18 @@ package io.reactivex.internal.operators.observable; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.*; -import io.reactivex.functions.Function; +import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; @@ -498,4 +499,26 @@ public void onNext(Integer t) { to.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); } + + @Test + public void noCancelPrevious() { + final AtomicInteger counter = new AtomicInteger(); + + Observable.range(1, 5) + .concatMap(new Function>() { + @Override + public ObservableSource apply(Integer v) throws Exception { + return Observable.just(v).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + + assertEquals(0, counter.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatTest.java index a9b6dc6faa..d47c684cfc 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatTest.java @@ -28,6 +28,7 @@ import io.reactivex.Observer; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; import io.reactivex.observers.*; @@ -155,7 +156,6 @@ public void testNestedAsyncConcat() throws InterruptedException { final CountDownLatch parentHasStarted = new CountDownLatch(1); final CountDownLatch parentHasFinished = new CountDownLatch(1); - Observable> observableOfObservables = Observable.unsafeCreate(new ObservableSource>() { @Override @@ -623,6 +623,7 @@ public Observable apply(Integer v) { inOrder.verify(o).onSuccess(list); verify(o, never()).onError(any(Throwable.class)); } + @Test public void concatVeryLongObservableOfObservablesTakeHalf() { final int n = 10000; @@ -1155,4 +1156,42 @@ public void onComplete() { assertTrue(disposable[0].isDisposed()); } + + @SuppressWarnings("unchecked") + @Test + public void noCancelPreviousArray() { + final AtomicInteger counter = new AtomicInteger(); + + Observable source = Observable.just(1).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + Observable.concatArray(source, source, source, source, source) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @SuppressWarnings("unchecked") + @Test + public void noCancelPreviousIterable() { + final AtomicInteger counter = new AtomicInteger(); + + Observable source = Observable.just(1).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + Observable.concat(Arrays.asList(source, source, source, source, source)) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybeTest.java index cbbed97785..3d03c3d8a1 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybeTest.java @@ -42,7 +42,6 @@ public void run() throws Exception { to.assertResult(1, 2, 3, 4, 5, 100); } - @Test public void normalNonEmpty() { final TestObserver to = new TestObserver(); 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 9edae7bdc9..aa24f68cd9 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java @@ -13,9 +13,7 @@ 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; @@ -239,6 +237,7 @@ public Observable apply(Integer t1) { verify(o, never()).onComplete(); verify(o).onError(any(TestException.class)); } + @Test public void debounceTimedLastIsNotLost() { PublishSubject source = PublishSubject.create(); @@ -256,6 +255,7 @@ public void debounceTimedLastIsNotLost() { verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test public void debounceSelectorLastIsNotLost() { PublishSubject source = PublishSubject.create(); @@ -504,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/ObservableDelaySubscriptionOtherTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDelaySubscriptionOtherTest.java index 6c416421a7..a37a2b00a7 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDelaySubscriptionOtherTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDelaySubscriptionOtherTest.java @@ -203,7 +203,6 @@ public Object apply(Observable o) throws Exception { }, false, 1, 1, 1); } - @Test public void afterDelayNoInterrupt() { ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); 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 a5017c68c4..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; @@ -24,11 +23,56 @@ import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; +@SuppressWarnings("deprecation") public class ObservableDematerializeTest { + @Test + public void simpleSelector() { + Observable> notifications = Observable.just(1, 2).materialize(); + Observable dematerialize = notifications.dematerialize(Functions.>identity()); + + Observer observer = TestHelper.mockObserver(); + + dematerialize.subscribe(observer); + + verify(observer, times(1)).onNext(1); + verify(observer, times(1)).onNext(2); + verify(observer, times(1)).onComplete(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void selectorCrash() { + Observable.just(1, 2) + .materialize() + .dematerialize(new Function, Notification>() { + @Override + public Notification apply(Notification v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void selectorNull() { + Observable.just(1, 2) + .materialize() + .dematerialize(new Function, Notification>() { + @Override + public Notification apply(Notification v) throws Exception { + return null; + } + }) + .test() + .assertFailure(NullPointerException.class); + } + @Test public void testDematerialize1() { Observable> notifications = Observable.just(1, 2).materialize(); @@ -175,4 +219,19 @@ protected void subscribeActual(Observer observer) { RxJavaPlugins.reset(); } } + + @Test + public void nonNotificationInstanceAfterDispose() { + new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(Notification.createOnComplete()); + observer.onNext(1); + } + } + .dematerialize() + .test() + .assertResult(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDetachTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDetachTest.java index 8a9c9f6daa..ab20fccc38 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDetachTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDetachTest.java @@ -24,7 +24,6 @@ import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; - public class ObservableDetachTest { Object o; @@ -85,7 +84,6 @@ public void range() { to.assertComplete(); } - @Test @Ignore("Observable doesn't do backpressure") public void backpressured() throws Exception { 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 83ba70675b..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; @@ -140,7 +139,7 @@ public void testDistinctUntilChangedOfSourceWithExceptionsFromKeySelector() { @Test public void customComparator() { - Observable source = Observable.just("a", "b", "B", "A","a", "C"); + Observable source = Observable.just("a", "b", "B", "A", "a", "C"); TestObserver to = TestObserver.create(); @@ -159,7 +158,7 @@ public boolean test(String a, String b) { @Test public void customComparatorThrows() { - Observable source = Observable.just("a", "b", "B", "A","a", "C"); + Observable source = Observable.just("a", "b", "B", "A", "a", "C"); TestObserver to = TestObserver.create(); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoFinallyTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoFinallyTest.java index 82738bd4de..d6f08fff27 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoFinallyTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoFinallyTest.java @@ -159,7 +159,6 @@ public void asyncFusedBoundary() { assertEquals(1, calls); } - @Test public void normalJustConditional() { Observable.just(1) @@ -445,7 +444,6 @@ public void onComplete() { assertEquals(1, calls); } - @Test public void eventOrdering() { final List list = new ArrayList(); 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/ObservableDoOnUnsubscribeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnUnsubscribeTest.java index 9d2c84db3c..b7df811bac 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnUnsubscribeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnUnsubscribeTest.java @@ -25,6 +25,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.functions.*; import io.reactivex.observers.TestObserver; +import io.reactivex.subjects.BehaviorSubject; public class ObservableDoOnUnsubscribeTest { @@ -152,4 +153,24 @@ public void run() { assertEquals("There should exactly 1 un-subscription events for upper stream", 1, upperCount.get()); assertEquals("There should exactly 1 un-subscription events for lower stream", 1, lowerCount.get()); } + + @Test + public void noReentrantDispose() { + + final AtomicInteger disposeCalled = new AtomicInteger(); + + final BehaviorSubject s = BehaviorSubject.create(); + s.doOnDispose(new Action() { + @Override + public void run() throws Exception { + disposeCalled.incrementAndGet(); + s.onNext(2); + } + }) + .firstOrError() + .subscribe() + .dispose(); + + assertEquals(1, disposeCalled.get()); + } } 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/ObservableFlatMapCompletableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableTest.java index 84ff5fa5ec..bd7dc12cf6 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableTest.java @@ -166,7 +166,6 @@ public CompletableSource apply(Integer v) throws Exception { .assertFailure(TestException.class); } - @Test public void fusedObservable() { TestObserver to = ObserverFusion.newTest(QueueFuseable.ANY); @@ -332,7 +331,6 @@ public CompletableSource apply(Integer v) throws Exception { .assertFailure(TestException.class); } - @Test public void fused() { TestObserver to = ObserverFusion.newTest(QueueFuseable.ANY); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybeTest.java index d2f77576a3..56ecedc02b 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybeTest.java @@ -188,7 +188,7 @@ public MaybeSource apply(Integer v) throws Exception { @Test public void middleError() { - Observable.fromArray(new String[]{"1","a","2"}).flatMapMaybe(new Function>() { + Observable.fromArray(new String[]{"1", "a", "2"}).flatMapMaybe(new Function>() { @Override public MaybeSource apply(final String s) throws NumberFormatException { //return Single.just(Integer.valueOf(s)); //This works diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingleTest.java index 4a56c61795..5226fc594c 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingleTest.java @@ -175,7 +175,7 @@ public SingleSource apply(Integer v) throws Exception { @Test public void middleError() { - Observable.fromArray(new String[]{"1","a","2"}).flatMapSingle(new Function>() { + Observable.fromArray(new String[]{"1", "a", "2"}).flatMapSingle(new Function>() { @Override public SingleSource apply(final String s) throws NumberFormatException { //return Single.just(Integer.valueOf(s)); //This works 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 17382f4dad..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.*; @@ -26,14 +25,14 @@ import io.reactivex.*; import io.reactivex.Observable; import io.reactivex.Observer; -import io.reactivex.disposables.Disposable; +import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; -import io.reactivex.subjects.PublishSubject; +import io.reactivex.subjects.*; public class ObservableFlatMapTest { @Test @@ -205,7 +204,6 @@ public void testFlatMapTransformsException() { Observable. error(new RuntimeException("Forced failure!")) ); - Observer o = TestHelper.mockObserver(); source.flatMap(just(onNext), just(onError), just0(onComplete)).subscribe(o); @@ -350,6 +348,7 @@ public Observable apply(Integer t1) { Assert.assertEquals(expected.size(), to.valueCount()); Assert.assertTrue(expected.containsAll(to.values())); } + @Test public void testFlatMapSelectorMaxConcurrent() { final int m = 4; @@ -471,6 +470,7 @@ public Observable apply(Integer t) { } } } + @Test(timeout = 30000) public void flatMapRangeMixedAsyncLoop() { for (int i = 0; i < 2000; i++) { @@ -514,7 +514,7 @@ public Observable apply(Integer t) { @Test public void flatMapIntPassthruAsync() { - for (int i = 0;i < 1000; i++) { + for (int i = 0; i < 1000; i++) { TestObserver to = new TestObserver(); Observable.range(1, 1000).flatMap(new Function>() { @@ -530,6 +530,7 @@ public Observable apply(Integer t) { to.assertValueCount(1000); } } + @Test public void flatMapTwoNestedSync() { for (final int n : new int[] { 1, 1000, 1000000 }) { @@ -895,7 +896,6 @@ public Object apply(Integer v, Object w) throws Exception { .assertFailureAndMessage(NullPointerException.class, "The mapper returned a null ObservableSource"); } - @Test public void failingFusedInnerCancelsSource() { final AtomicInteger counter = new AtomicInteger(); @@ -1005,4 +1005,140 @@ public void onNext(Integer t) { to.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); } + + @Test + public void fusedSourceCrashResumeWithNextSource() { + final UnicastSubject fusedSource = UnicastSubject.create(); + TestObserver to = new TestObserver(); + + ObservableFlatMap.MergeObserver merger = + new ObservableFlatMap.MergeObserver(to, new Function>() { + @Override + public Observable apply(Integer t) + throws Exception { + if (t == 0) { + return fusedSource + .map(new Function() { + @Override + public Integer apply(Integer v) + throws Exception { throw new TestException(); } + }) + .compose(TestHelper.observableStripBoundary()); + } + return Observable.range(10 * t, 5); + } + }, true, Integer.MAX_VALUE, 128); + + merger.onSubscribe(Disposables.empty()); + merger.getAndIncrement(); + + merger.onNext(0); + merger.onNext(1); + merger.onNext(2); + + assertTrue(fusedSource.hasObservers()); + + fusedSource.onNext(-1); + + merger.drainLoop(); + + to.assertValuesOnly(10, 11, 12, 13, 14, 20, 21, 22, 23, 24); + } + + @Test + public void maxConcurrencySustained() { + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + PublishSubject ps3 = PublishSubject.create(); + PublishSubject ps4 = PublishSubject.create(); + + TestObserver to = Observable.just(ps1, ps2, ps3, ps4) + .flatMap(new Function, ObservableSource>() { + @Override + public ObservableSource apply(PublishSubject v) throws Exception { + return v; + } + }, 2) + .doOnNext(new Consumer() { + @Override + public void accept(Integer v) throws Exception { + if (v == 1) { + // this will make sure the drain loop detects two completed + // inner sources and replaces them with fresh ones + ps1.onComplete(); + ps2.onComplete(); + } + } + }) + .test(); + + ps1.onNext(1); + + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + assertTrue(ps3.hasObservers()); + assertTrue(ps4.hasObservers()); + + to.dispose(); + + 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 56d7b7ee47..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; @@ -310,7 +309,4 @@ public Object call() throws Exception { .test() .assertResult(1); } - - - } 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/ObservableMergeDelayErrorTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeDelayErrorTest.java index 472a25300b..833f9105c5 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeDelayErrorTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeDelayErrorTest.java @@ -429,6 +429,7 @@ public void onNext(String args) { } } + @Test @Ignore("Subscribers should not throw") public void testMergeSourceWhichDoesntPropagateExceptionBack() { diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeMaxConcurrentTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeMaxConcurrentTest.java index 582421d42b..ef52095f25 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeMaxConcurrentTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeMaxConcurrentTest.java @@ -137,6 +137,7 @@ public void testMergeALotOfSourcesOneByOneSynchronously() { } assertEquals(j, n); } + @Test public void testMergeALotOfSourcesOneByOneSynchronouslyTakeHalf() { int n = 10000; @@ -171,6 +172,7 @@ public void testSimple() { to.assertValueSequence(result); } } + @Test public void testSimpleOneLess() { for (int i = 2; i < 100; i++) { @@ -189,6 +191,7 @@ public void testSimpleOneLess() { to.assertValueSequence(result); } } + @Test//(timeout = 20000) public void testSimpleAsyncLoop() { IoScheduler ios = (IoScheduler)Schedulers.io(); @@ -201,6 +204,7 @@ public void testSimpleAsyncLoop() { } } } + @Test(timeout = 30000) public void testSimpleAsync() { for (int i = 1; i < 50; i++) { @@ -221,12 +225,14 @@ public void testSimpleAsync() { assertEquals(expected, actual); } } + @Test(timeout = 30000) public void testSimpleOneLessAsyncLoop() { for (int i = 0; i < 200; i++) { testSimpleOneLessAsync(); } } + @Test(timeout = 30000) public void testSimpleOneLessAsync() { long t = System.currentTimeMillis(); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeTest.java index c178b3bddc..8b69ef593b 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeTest.java @@ -317,8 +317,8 @@ public void testError2() { // we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("four", null, "six")); // we expect to lose "six" - final Observable o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight", null));// we expect to lose all of these since o2 is done first and fails - final Observable o4 = Observable.unsafeCreate(new TestErrorObservable("nine"));// we expect to lose all of these since o2 is done first and fails + final Observable o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight", null)); // we expect to lose all of these since o2 is done first and fails + final Observable o4 = Observable.unsafeCreate(new TestErrorObservable("nine")); // we expect to lose all of these since o2 is done first and fails Observable m = Observable.merge(o1, o2, o3, o4); m.subscribe(stringObserver); @@ -1082,10 +1082,12 @@ void runMerge(Function> func, TestObserver public void testFastMergeFullScalar() { runMerge(toScalar, new TestObserver()); } + @Test public void testFastMergeHiddenScalar() { runMerge(toHiddenScalar, new TestObserver()); } + @Test public void testSlowMergeFullScalar() { for (final int req : new int[] { 16, 32, 64, 128, 256 }) { @@ -1103,6 +1105,7 @@ public void onNext(Integer t) { runMerge(toScalar, to); } } + @Test public void testSlowMergeHiddenScalar() { for (final int req : new int[] { 16, 32, 64, 128, 256 }) { 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 1ac99fcb5e..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,9 +29,10 @@ 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; import io.reactivex.internal.operators.observable.ObservableObserveOn.ObserveOnObserver; import io.reactivex.internal.schedulers.ImmediateThinScheduler; import io.reactivex.observers.*; @@ -740,4 +741,109 @@ public void onNext(Integer t) { }) .assertValuesOnly(2, 3); } + + @Test + public void workerNotDisposedPrematurelyNormalInNormalOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + Observable.concat( + Observable.just(1).hide().observeOn(s), + Observable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelySyncInNormalOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + Observable.concat( + Observable.just(1).observeOn(s), + Observable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelyAsyncInNormalOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + UnicastSubject up = UnicastSubject.create(); + up.onNext(1); + up.onComplete(); + + Observable.concat( + up.observeOn(s), + Observable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + static final class TestObserverFusedCanceling + extends TestObserver { + + TestObserverFusedCanceling() { + super(); + initialFusionMode = QueueFuseable.ANY; + } + + @Override + public void onComplete() { + cancel(); + super.onComplete(); + } + } + + @Test + public void workerNotDisposedPrematurelyNormalInAsyncOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + TestObserver to = new TestObserverFusedCanceling(); + + Observable.just(1).hide().observeOn(s).subscribe(to); + + 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/ObservableOnExceptionResumeNextViaObservableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableOnExceptionResumeNextViaObservableTest.java index 173f506bce..50312e3884 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableOnExceptionResumeNextViaObservableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableOnExceptionResumeNextViaObservableTest.java @@ -183,7 +183,6 @@ public String apply(String s) { verify(observer, times(1)).onComplete(); } - @Test public void testBackpressure() { TestObserver to = new TestObserver(); @@ -212,7 +211,6 @@ public Integer apply(Integer t1) { to.assertNoErrors(); } - private static class TestObservable implements ObservableSource { final String[] values; 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, ObservableSource>() { + @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 18251f8f78..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(); @@ -372,6 +393,7 @@ public void subscribe(Observer t) { assertEquals(2, calls.get()); } + @Test public void testObserveOn() { ConnectableObservable co = Observable.range(0, 1000).publish(); 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 5dce27dcaa..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; @@ -145,7 +144,6 @@ public void testBackpressureWithInitialValueObservable() throws InterruptedExcep assertEquals(21, r.intValue()); } - @Test public void testAggregateAsIntSum() { 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 37efa77570..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(); @@ -516,7 +536,7 @@ public Integer apply(Integer t1, Integer t2) { public void testUpstreamErrorAllowsRetry() throws InterruptedException { final AtomicInteger intervalSubscribed = new AtomicInteger(); Observable interval = - Observable.interval(200,TimeUnit.MILLISECONDS) + Observable.interval(200, TimeUnit.MILLISECONDS) .doOnSubscribe(new Consumer() { @Override public void accept(Disposable d) { @@ -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(); @@ -765,15 +785,17 @@ public void replayIsUnsubscribed() { ConnectableObservable co = Observable.just(1).concatWith(Observable.never()) .replay(); - assertTrue(((Disposable)co).isDisposed()); + if (co instanceof Disposable) { + assertTrue(((Disposable)co).isDisposed()); - Disposable connection = co.connect(); + Disposable connection = co.connect(); - assertFalse(((Disposable)co).isDisposed()); + assertFalse(((Disposable)co).isDisposed()); - connection.dispose(); + connection.dispose(); - assertTrue(((Disposable)co).isDisposed()); + assertTrue(((Disposable)co).isDisposed()); + } } static final class BadObservableSubscribe extends ConnectableObservable { @@ -1239,6 +1261,8 @@ public void cancelTerminateStateExclusion() { o.cancel(null); + o.cancel(new RefConnection(o)); + RefConnection rc = new RefConnection(o); o.connection = null; rc.subscriberCount = 0; @@ -1274,5 +1298,105 @@ public void cancelTerminateStateExclusion() { rc.connected = true; o.connection = rc; 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 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 3616ef729f..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.*; @@ -30,6 +29,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; @@ -324,4 +324,129 @@ public Object apply(Object w) throws Exception { .test() .assertFailure(TestException.class, 1, 2, 3); } + + @Test + public void noCancelPreviousRepeat() { + final AtomicInteger counter = new AtomicInteger(); + + Observable source = Observable.just(1).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.repeat(5) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatUntil() { + final AtomicInteger counter = new AtomicInteger(); + + Observable source = Observable.just(1).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + final AtomicInteger times = new AtomicInteger(); + + source.repeatUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return times.getAndIncrement() == 4; + } + }) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatWhen() { + final AtomicInteger counter = new AtomicInteger(); + + Observable source = Observable.just(1).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + final AtomicInteger times = new AtomicInteger(); + + source.repeatWhen(new Function, ObservableSource>() { + @Override + public ObservableSource apply(Observable e) throws Exception { + return e.takeWhile(new Predicate() { + @Override + public boolean test(Object v) throws Exception { + return times.getAndIncrement() < 4; + } + }); + } + }) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @Test + public void repeatFloodNoSubscriptionError() { + List errors = TestHelper.trackPluginErrors(); + + try { + final PublishSubject source = PublishSubject.create(); + final PublishSubject signaller = PublishSubject.create(); + + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + TestObserver to = source.take(1) + .repeatWhen(new Function, ObservableSource>() { + @Override + public ObservableSource apply(Observable v) + throws Exception { + return signaller; + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + source.onNext(1); + } + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + signaller.onNext(1); + } + } + }; + + TestHelper.race(r1, r2); + + to.dispose(); + } + + if (!errors.isEmpty()) { + for (Throwable e : errors) { + e.printStackTrace(); + } + fail(errors + ""); + } + } finally { + RxJavaPlugins.reset(); + } + } } 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 0c62893a77..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,12 +14,12 @@ 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.*; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import org.junit.*; import org.mockito.InOrder; @@ -506,7 +506,6 @@ public void run() { } } - /* * test the basic expectation of OperatorMulticast via replay */ @@ -521,7 +520,7 @@ public void testIssue2191_UnsubscribeSource() throws Exception { Observer spiedSubscriberAfterConnect = TestHelper.mockObserver(); // Observable under test - Observable source = Observable.just(1,2); + Observable source = Observable.just(1, 2); ConnectableObservable replay = source .doOnNext(sourceNext) @@ -644,7 +643,6 @@ public void testIssue2191_SchedulerUnsubscribeOnError() throws Exception { verify(mockObserverBeforeConnect).onSubscribe((Disposable)any()); verify(mockObserverAfterConnect).onSubscribe((Disposable)any()); - mockScheduler.advanceTimeBy(1, TimeUnit.SECONDS); // verify interactions verify(sourceNext, times(1)).accept(1); @@ -681,7 +679,6 @@ public static Worker workerSpy(final Disposable mockDisposable) { return spy(new InprocessWorker(mockDisposable)); } - static class InprocessWorker extends Worker { private final Disposable mockDisposable; public boolean unsubscribed; @@ -941,11 +938,11 @@ public void accept(String v) { @Test public void testUnsubscribeSource() throws Exception { Action unsubscribe = mock(Action.class); - Observable o = Observable.just(1).doOnDispose(unsubscribe).cache(); + Observable o = Observable.just(1).doOnDispose(unsubscribe).replay().autoConnect(); o.subscribe(); o.subscribe(); o.subscribe(); - verify(unsubscribe, times(1)).run(); + verify(unsubscribe, never()).run(); } @Test @@ -986,6 +983,7 @@ public void testAsync() { assertEquals(10000, to2.values().size()); } } + @Test public void testAsyncComeAndGo() { Observable source = Observable.interval(1, 1, TimeUnit.MILLISECONDS) @@ -1052,7 +1050,6 @@ public void testValuesAndThenError() { .concatWith(Observable.error(new TestException())) .replay().autoConnect(); - TestObserver to = new TestObserver(); source.subscribe(to); @@ -1716,4 +1713,66 @@ public void noHeadRetentionTime() { assertSame(o, buf.get()); } -} + + @Test + public void noBoundedRetentionViaThreadLocal() throws Exception { + Observable source = Observable.range(1, 200) + .map(new Function() { + @Override + public byte[] apply(Integer v) throws Exception { + return new byte[1024 * 1024]; + } + }) + .replay(new Function, Observable>() { + @Override + public Observable apply(final Observable o) throws Exception { + return o.take(1) + .concatMap(new Function>() { + @Override + public Observable apply(byte[] v) throws Exception { + return o; + } + }); + } + }, 1) + .takeLast(1) + ; + + System.out.println("Bounded Replay Leak check: Wait before GC"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC"); + System.gc(); + + Thread.sleep(500); + + final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); + long initial = memHeap.getUsed(); + + System.out.printf("Bounded Replay Leak check: Starting: %.3f MB%n", initial / 1024.0 / 1024.0); + + final AtomicLong after = new AtomicLong(); + + source.subscribe(new Consumer() { + @Override + public void accept(byte[] v) throws Exception { + System.out.println("Bounded Replay Leak check: Wait before GC 2"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC 2"); + System.gc(); + + Thread.sleep(500); + + after.set(memoryMXBean.getHeapMemoryUsage().getUsed()); + } + }); + + System.out.printf("Bounded Replay Leak check: After: %.3f MB%n", after.get() / 1024.0 / 1024.0); + + if (initial + 100 * 1024 * 1024 < after.get()) { + Assert.fail("Bounded Replay Leak check: Memory leak detected: " + (initial / 1024.0 / 1024.0) + + " -> " + after.get() / 1024.0 / 1024.0); + } + }} 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 9ebb3fd450..ab7998c8a1 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java @@ -29,9 +29,11 @@ import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.observables.GroupedObservable; import io.reactivex.observers.*; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; @@ -522,11 +524,14 @@ static final class SlowObservable implements ObservableSource { final AtomicInteger active = new AtomicInteger(0), maxActive = new AtomicInteger(0); final AtomicInteger nextBeforeFailure; + final String context; + private final int emitDelay; - SlowObservable(int emitDelay, int countNext) { + SlowObservable(int emitDelay, int countNext, String context) { this.emitDelay = emitDelay; this.nextBeforeFailure = new AtomicInteger(countNext); + this.context = context; } @Override @@ -542,7 +547,7 @@ public void run() { efforts.getAndIncrement(); active.getAndIncrement(); maxActive.set(Math.max(active.get(), maxActive.get())); - final Thread thread = new Thread() { + final Thread thread = new Thread(context) { @Override public void run() { long nr = 0; @@ -552,7 +557,9 @@ public void run() { if (nextBeforeFailure.getAndDecrement() > 0) { observer.onNext(nr++); } else { + active.decrementAndGet(); observer.onError(new RuntimeException("expected-failed")); + break; } } } catch (InterruptedException t) { @@ -613,7 +620,7 @@ public void testUnsubscribeAfterError() { Observer observer = TestHelper.mockObserver(); // Observable that always fails after 100ms - SlowObservable so = new SlowObservable(100, 0); + SlowObservable so = new SlowObservable(100, 0, "testUnsubscribeAfterError"); Observable o = Observable.unsafeCreate(so).retry(5); AsyncObserver async = new AsyncObserver(observer); @@ -637,7 +644,7 @@ public void testTimeoutWithRetry() { Observer observer = TestHelper.mockObserver(); // Observable that sends every 100ms (timeout fails instead) - SlowObservable so = new SlowObservable(100, 10); + SlowObservable so = new SlowObservable(100, 10, "testTimeoutWithRetry"); Observable o = Observable.unsafeCreate(so).timeout(80, TimeUnit.MILLISECONDS).retry(5); AsyncObserver async = new AsyncObserver(observer); @@ -779,6 +786,7 @@ static StringBuilder sequenceFrequency(Iterable it) { return sb; } + @Test//(timeout = 3000) public void testIssue1900() throws InterruptedException { Observer observer = TestHelper.mockObserver(); @@ -800,7 +808,7 @@ public String apply(String t1) { return t1; } }) - .flatMap(new Function, Observable>() { + .flatMap(new Function, Observable>() { @Override public Observable apply(GroupedObservable t1) { return t1.take(1); @@ -819,6 +827,7 @@ public Observable apply(GroupedObservable t1) { inOrder.verify(observer, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } + @Test//(timeout = 3000) public void testIssue1900SourceNotSupportingBackpressure() { Observer observer = TestHelper.mockObserver(); @@ -844,7 +853,7 @@ public String apply(String t1) { return t1; } }) - .flatMap(new Function, Observable>() { + .flatMap(new Function, Observable>() { @Override public Observable apply(GroupedObservable t1) { return t1.take(1); @@ -929,4 +938,257 @@ public ObservableSource apply(Throwable ignore) throws Exception { assertFalse(subject.hasObservers()); } + @Test + public void noCancelPreviousRetry() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Observable source = Observable.defer(new Callable>() { + @Override + public ObservableSource call() throws Exception { + if (times.getAndIncrement() < 4) { + return Observable.error(new TestException()); + } + return Observable.just(1); + } + }) + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retry(5) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRetryWhile() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Observable source = Observable.defer(new Callable>() { + @Override + public ObservableSource call() throws Exception { + if (times.getAndIncrement() < 4) { + return Observable.error(new TestException()); + } + return Observable.just(1); + } + }) + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retry(5, Functions.alwaysTrue()) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRetryWhile2() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Observable source = Observable.defer(new Callable>() { + @Override + public ObservableSource call() throws Exception { + if (times.getAndIncrement() < 4) { + return Observable.error(new TestException()); + } + return Observable.just(1); + } + }) + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retry(new BiPredicate() { + @Override + public boolean test(Integer a, Throwable b) throws Exception { + return a < 5; + } + }) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRetryUntil() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Observable source = Observable.defer(new Callable>() { + @Override + public ObservableSource call() throws Exception { + if (times.getAndIncrement() < 4) { + return Observable.error(new TestException()); + } + return Observable.just(1); + } + }) + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retryUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return false; + } + }) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatWhen() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Observable source = Observable.defer(new Callable>() { + @Override + public ObservableSource call() throws Exception { + if (times.get() < 4) { + return Observable.error(new TestException()); + } + return Observable.just(1); + } + }).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retryWhen(new Function, ObservableSource>() { + @Override + public ObservableSource apply(Observable e) throws Exception { + return e.takeWhile(new Predicate() { + @Override + public boolean test(Object v) throws Exception { + return times.getAndIncrement() < 4; + } + }); + } + }) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatWhen2() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Observable source = Observable.error(new TestException()).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retryWhen(new Function, ObservableSource>() { + @Override + public ObservableSource apply(Observable e) throws Exception { + return e.takeWhile(new Predicate() { + @Override + public boolean test(Object v) throws Exception { + return times.getAndIncrement() < 4; + } + }); + } + }) + .test() + .assertResult(); + + assertEquals(0, counter.get()); + } + + @Test + public void repeatFloodNoSubscriptionError() { + List errors = TestHelper.trackPluginErrors(); + + final TestException error = new TestException(); + + try { + final PublishSubject source = PublishSubject.create(); + final PublishSubject signaller = PublishSubject.create(); + + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + TestObserver to = source.take(1) + .map(new Function() { + @Override + public Integer apply(Integer v) throws Exception { + throw error; + } + }) + .retryWhen(new Function, ObservableSource>() { + @Override + public ObservableSource apply(Observable v) + throws Exception { + return signaller; + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + source.onNext(1); + } + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + signaller.onNext(1); + } + } + }; + + TestHelper.race(r1, r2); + + to.dispose(); + } + + if (!errors.isEmpty()) { + for (Throwable e : errors) { + e.printStackTrace(); + } + fail(errors + ""); + } + } finally { + RxJavaPlugins.reset(); + } + } } 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 79ca287629..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; @@ -69,6 +68,7 @@ public void testWithNothingToRetry() { inOrder.verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test public void testRetryTwice() { Observable source = Observable.unsafeCreate(new ObservableSource() { @@ -104,6 +104,7 @@ public void subscribe(Observer t1) { verify(o, never()).onError(any(Throwable.class)); } + @Test public void testRetryTwiceAndGiveUp() { Observable source = Observable.unsafeCreate(new ObservableSource() { @@ -131,6 +132,7 @@ public void subscribe(Observer t1) { verify(o, never()).onComplete(); } + @Test public void testRetryOnSpecificException() { Observable source = Observable.unsafeCreate(new ObservableSource() { @@ -165,6 +167,7 @@ public void subscribe(Observer t1) { inOrder.verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test public void testRetryOnSpecificExceptionAndNotOther() { final IOException ioe = new IOException(); @@ -225,7 +228,7 @@ public void testUnsubscribeAfterError() { Observer observer = TestHelper.mockObserver(); // Observable that always fails after 100ms - ObservableRetryTest.SlowObservable so = new ObservableRetryTest.SlowObservable(100, 0); + ObservableRetryTest.SlowObservable so = new ObservableRetryTest.SlowObservable(100, 0, "testUnsubscribeAfterError"); Observable o = Observable .unsafeCreate(so) .retry(retry5); @@ -251,7 +254,7 @@ public void testTimeoutWithRetry() { Observer observer = TestHelper.mockObserver(); // Observable that sends every 100ms (timeout fails instead) - ObservableRetryTest.SlowObservable so = new ObservableRetryTest.SlowObservable(100, 10); + ObservableRetryTest.SlowObservable so = new ObservableRetryTest.SlowObservable(100, 10, "testTimeoutWithRetry"); Observable o = Observable .unsafeCreate(so) .timeout(80, TimeUnit.MILLISECONDS) @@ -288,6 +291,7 @@ public Integer apply(Integer t1) { assertEquals(6, c.get()); assertEquals(Collections.singletonList(e), to.errors()); } + @Test public void testJustAndRetry() throws Exception { final AtomicBoolean throwException = new AtomicBoolean(true); @@ -329,7 +333,7 @@ public void accept(Long t) { System.out.println(t); list.add(t); }}); - assertEquals(Arrays.asList(1L,1L,2L,3L), list); + assertEquals(Arrays.asList(1L, 1L, 2L, 3L), list); } @Test @@ -353,7 +357,7 @@ public void accept(Long t) { System.out.println(t); list.add(t); }}); - assertEquals(Arrays.asList(1L,1L,2L,3L), list); + assertEquals(Arrays.asList(1L, 1L, 2L, 3L), list); } @Test 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 8cd3aa75b6..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.*; @@ -326,7 +325,7 @@ public void subscribe(Observer o) { o.onNext(2); o.onError(err2); }}) - .scan(new BiFunction() { + .scan(new BiFunction() { @Override public Integer apply(Integer t1, Integer t2) throws Exception { throw err; @@ -351,7 +350,7 @@ public void subscribe(Observer o) { o.onNext(2); o.onComplete(); }}) - .scan(new BiFunction() { + .scan(new BiFunction() { @Override public Integer apply(Integer t1, Integer t2) throws Exception { throw err; @@ -374,7 +373,7 @@ public void subscribe(Observer o) { o.onNext(2); o.onNext(3); }}) - .scan(new BiFunction() { + .scan(new BiFunction() { @Override public Integer apply(Integer t1, Integer t2) throws Exception { count.incrementAndGet(); 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/ObservableSkipTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipTest.java index d787de26bf..9b1de8ab4e 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipTest.java @@ -142,7 +142,7 @@ public void testRequestOverflowDoesNotOccur() { to.assertTerminated(); to.assertComplete(); to.assertNoErrors(); - assertEquals(Arrays.asList(6,7,8,9,10), to.values()); + assertEquals(Arrays.asList(6, 7, 8, 9, 10), to.values()); } @Test 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/ObservableSwitchIfEmptyTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmptyTest.java index 20c5018d01..83024d1ea5 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmptyTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmptyTest.java @@ -25,7 +25,6 @@ import io.reactivex.functions.Consumer; import io.reactivex.observers.DefaultObserver; - public class ObservableSwitchIfEmptyTest { @Test @@ -90,7 +89,6 @@ public void onNext(Long aLong) { } }).subscribe(); - assertTrue(d.isDisposed()); // FIXME no longer assertable // assertTrue(sub.isUnsubscribed()); 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 ab68bd6790..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 { @@ -484,7 +486,6 @@ public void onNext(String t) { Assert.assertEquals(250, to.valueCount()); } - @Test public void delayErrors() { PublishSubject> source = PublishSubject.create(); @@ -608,7 +609,6 @@ public ObservableSource apply(Object v) throws Exception { } - @Test public void switchMapInnerCancelled() { PublishSubject ps = PublishSubject.create(); @@ -1194,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/ObservableTakeLastOneTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastOneTest.java index 859e311a2d..7dc3a1cf39 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastOneTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastOneTest.java @@ -82,7 +82,7 @@ public void run() { public void testTakeLastZeroProcessesAllItemsButIgnoresThem() { final AtomicInteger upstreamCount = new AtomicInteger(); final int num = 10; - long count = Observable.range(1,num).doOnNext(new Consumer() { + long count = Observable.range(1, num).doOnNext(new Consumer() { @Override public void accept(Integer t) { 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 27327bffcf..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; @@ -179,7 +178,7 @@ public void onNext(Integer integer) { cancel(); } }); - assertEquals(1,count.get()); + assertEquals(1, count.get()); } @Test 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 722da7890a..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; @@ -46,6 +45,7 @@ public boolean test(Object v) { verify(o, never()).onError(any(Throwable.class)); verify(o).onComplete(); } + @Test public void takeAll() { Observer o = TestHelper.mockObserver(); @@ -62,6 +62,7 @@ public boolean test(Integer v) { verify(o, never()).onError(any(Throwable.class)); verify(o).onComplete(); } + @Test public void takeFirst() { Observer o = TestHelper.mockObserver(); @@ -78,6 +79,7 @@ public boolean test(Integer v) { verify(o, never()).onError(any(Throwable.class)); verify(o).onComplete(); } + @Test public void takeSome() { Observer o = TestHelper.mockObserver(); @@ -96,6 +98,7 @@ public boolean test(Integer t1) { verify(o, never()).onError(any(Throwable.class)); verify(o).onComplete(); } + @Test public void functionThrows() { Observer o = TestHelper.mockObserver(); @@ -114,6 +117,7 @@ public boolean test(Integer t1) { verify(o).onError(any(TestException.class)); verify(o, never()).onComplete(); } + @Test public void sourceThrows() { Observer o = TestHelper.mockObserver(); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilTest.java index d78129e2ea..7ea5698be3 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilTest.java @@ -210,6 +210,7 @@ public void testUntilFires() { // 2.0.2 - not anymore // assertTrue("Not cancelled!", ts.isCancelled()); } + @Test public void testMainCompletes() { PublishSubject source = PublishSubject.create(); @@ -234,6 +235,7 @@ public void testMainCompletes() { // 2.0.2 - not anymore // assertTrue("Not cancelled!", ts.isCancelled()); } + @Test public void testDownstreamUnsubscribes() { PublishSubject source = PublishSubject.create(); @@ -273,7 +275,6 @@ public Observable apply(Observable o) throws Exception { }); } - @Test public void untilPublisherMainSuccess() { PublishSubject main = PublishSubject.create(); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableThrottleLatestTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableThrottleLatestTest.java index 9d60e5d232..059d516da1 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableThrottleLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableThrottleLatestTest.java @@ -129,7 +129,6 @@ public void normal() { to.assertResult(1, 3, 5, 6); } - @Test public void normalEmitLast() { TestScheduler sch = new TestScheduler(); 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 21e8caea84..9d517ec1be 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutTests.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutTests.java @@ -13,8 +13,8 @@ package io.reactivex.internal.operators.observable; +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; @@ -81,27 +81,23 @@ public void shouldNotTimeoutIfSecondOnNextWithinTimeout() { @Test public void shouldTimeoutIfOnNextNotWithinTimeout() { - Observer observer = TestHelper.mockObserver(); - TestObserver to = new TestObserver(observer); + TestObserver observer = new TestObserver(); - withTimeout.subscribe(to); + withTimeout.subscribe(observer); testScheduler.advanceTimeBy(TIMEOUT + 1, TimeUnit.SECONDS); - verify(observer).onError(any(TimeoutException.class)); - to.dispose(); + observer.assertFailureAndMessage(TimeoutException.class, timeoutMessage(TIMEOUT, TIME_UNIT)); } @Test public void shouldTimeoutIfSecondOnNextNotWithinTimeout() { - Observer observer = TestHelper.mockObserver(); - TestObserver to = new TestObserver(observer); + TestObserver observer = new TestObserver(); withTimeout.subscribe(observer); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); - verify(observer).onNext("One"); + observer.assertValue("One"); testScheduler.advanceTimeBy(TIMEOUT + 1, TimeUnit.SECONDS); - verify(observer).onError(any(TimeoutException.class)); - to.dispose(); + observer.assertFailureAndMessage(TimeoutException.class, timeoutMessage(TIMEOUT, TIME_UNIT), "One"); } @Test @@ -234,8 +230,7 @@ public void shouldTimeoutIfSynchronizedObservableEmitFirstOnNextNotWithinTimeout final CountDownLatch exit = new CountDownLatch(1); final CountDownLatch timeoutSetuped = new CountDownLatch(1); - final Observer observer = TestHelper.mockObserver(); - final TestObserver to = new TestObserver(observer); + final TestObserver observer = new TestObserver(); new Thread(new Runnable() { @@ -257,16 +252,14 @@ public void subscribe(Observer observer) { } }).timeout(1, TimeUnit.SECONDS, testScheduler) - .subscribe(to); + .subscribe(observer); } }).start(); timeoutSetuped.await(); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onError(isA(TimeoutException.class)); - inOrder.verifyNoMoreInteractions(); + observer.assertFailureAndMessage(TimeoutException.class, timeoutMessage(1, TimeUnit.SECONDS)); exit.countDown(); // exit the thread } @@ -286,15 +279,12 @@ public void subscribe(Observer observer) { TestScheduler testScheduler = new TestScheduler(); Observable observableWithTimeout = never.timeout(1000, TimeUnit.MILLISECONDS, testScheduler); - Observer observer = TestHelper.mockObserver(); - TestObserver to = new TestObserver(observer); - observableWithTimeout.subscribe(to); + TestObserver observer = new TestObserver(); + observableWithTimeout.subscribe(observer); testScheduler.advanceTimeBy(2000, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onError(isA(TimeoutException.class)); - inOrder.verifyNoMoreInteractions(); + observer.assertFailureAndMessage(TimeoutException.class, timeoutMessage(1000, TimeUnit.MILLISECONDS)); verify(upstream, times(1)).dispose(); } @@ -547,11 +537,13 @@ public void run() { if (to.valueCount() != 0) { if (to.errorCount() != 0) { to.assertFailure(TimeoutException.class, 1); + to.assertErrorMessage(timeoutMessage(1, TimeUnit.SECONDS)); } else { to.assertValuesOnly(1); } } else { to.assertFailure(TimeoutException.class); + to.assertErrorMessage(timeoutMessage(1, TimeUnit.SECONDS)); } } } 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 416a5e10e0..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; @@ -87,6 +86,7 @@ public void testTimerPeriodically() { to.assertNotComplete(); to.assertNoErrors(); } + @Test public void testInterval() { Observable w = Observable.interval(1, TimeUnit.SECONDS, scheduler); @@ -227,6 +227,7 @@ public void testWithMultipleStaggeredSubscribersAndPublish() { to2.assertNoErrors(); to2.assertNotComplete(); } + @Test public void testOnceObserverThrows() { Observable source = Observable.timer(100, TimeUnit.MILLISECONDS, scheduler); @@ -255,6 +256,7 @@ public void onComplete() { verify(observer, never()).onNext(anyLong()); verify(observer, never()).onComplete(); } + @Test public void testPeriodicObserverThrows() { Observable source = Observable.interval(100, 100, TimeUnit.MILLISECONDS, scheduler); 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/ObservableToMapTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableToMapTest.java index 1f477a0a38..f9eb6e1634 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableToMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableToMapTest.java @@ -225,7 +225,6 @@ public String apply(String v) { verify(objectObserver, times(1)).onError(any(Throwable.class)); } - @Test public void testToMap() { Observable source = Observable.just("a", "bb", "ccc", "dddd"); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableToMultimapTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableToMultimapTest.java index 9a1ebc44d8..f762930fc1 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableToMultimapTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableToMultimapTest.java @@ -296,8 +296,6 @@ public Map> call() { verify(objectObserver, never()).onComplete(); } - - @Test public void testToMultimap() { Observable source = Observable.just("a", "b", "cc", "dd"); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableToSortedListTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableToSortedListTest.java index f715b6b08c..20ffdd236a 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableToSortedListTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableToSortedListTest.java @@ -115,7 +115,6 @@ public int compare(Integer a, Integer b) { .assertResult(Arrays.asList(5, 4, 3, 2, 1)); } - @Test public void testSortedList() { Observable w = Observable.just(1, 3, 2, 5, 4); 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 cdb21dafa5..1067ff37c9 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java @@ -46,7 +46,10 @@ public void subscribe(Observer t1) { t1.onSubscribe(subscription); t1.onNext(1); t1.onNext(2); - t1.onComplete(); + // observeOn will prevent canceling the upstream upon its termination now + // this call is racing for that state in this test + // not doing it will make sure the unsubscribeOn always gets through + // t1.onComplete(); } }); @@ -69,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(); @@ -92,7 +95,10 @@ public void subscribe(Observer t1) { t1.onSubscribe(subscription); t1.onNext(1); t1.onNext(2); - t1.onComplete(); + // observeOn will prevent canceling the upstream upon its termination now + // this call is racing for that state in this test + // not doing it will make sure the unsubscribeOn always gets through + // t1.onComplete(); } }); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableUsingTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableUsingTest.java index 286bb7d4a9..02fd41ef54 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableUsingTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableUsingTest.java @@ -330,8 +330,6 @@ public Observable apply(Resource resource) { } - - @Test public void testUsingDisposesEagerlyBeforeError() { final List events = new ArrayList(); 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 000b116615..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.*; @@ -76,7 +75,6 @@ public void onComplete() { } source.onComplete(); - verify(o, never()).onError(any(Throwable.class)); assertEquals(n / 3, values.size()); @@ -329,6 +327,7 @@ public Observable call() { to.assertNoErrors(); to.assertValueCount(1); } + @Test public void testMainUnsubscribedOnBoundaryCompletion() { PublishSubject source = PublishSubject.create(); @@ -348,7 +347,6 @@ public Observable call() { boundary.onComplete(); - assertFalse(source.hasObservers()); assertFalse(boundary.hasObservers()); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithSizeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithSizeTest.java index 8ef033d5f1..af1503e8a7 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithSizeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithSizeTest.java @@ -204,7 +204,6 @@ private List list(String... args) { return list; } - public static Observable hotStream() { return Observable.unsafeCreate(new ObservableSource() { @Override diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithStartEndObservableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithStartEndObservableTest.java index d1426a5a61..c4f7fa6409 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithStartEndObservableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithStartEndObservableTest.java @@ -17,6 +17,7 @@ import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.*; @@ -256,8 +257,8 @@ public Observable apply(Integer t) { to.dispose(); - // FIXME subject has subscribers because of the open window - assertTrue(open.hasObservers()); + // Disposing the outer sequence stops the opening of new windows + assertFalse(open.hasObservers()); // FIXME subject has subscribers because of the open window assertTrue(close.hasObservers()); } @@ -423,4 +424,38 @@ protected void subscribeActual( RxJavaPlugins.reset(); } } + + static Observable observableDisposed(final AtomicBoolean ref) { + return Observable.just(1).concatWith(Observable.never()) + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + ref.set(true); + } + }); + } + + @Test + public void mainAndBoundaryDisposeOnNoWindows() { + AtomicBoolean mainDisposed = new AtomicBoolean(); + AtomicBoolean openDisposed = new AtomicBoolean(); + final AtomicBoolean closeDisposed = new AtomicBoolean(); + + observableDisposed(mainDisposed) + .window(observableDisposed(openDisposed), new Function>() { + @Override + public ObservableSource apply(Integer v) throws Exception { + return observableDisposed(closeDisposed); + } + }) + .test() + .assertSubscribed() + .assertNoErrors() + .assertNotComplete() + .dispose(); + + assertTrue(mainDisposed.get()); + assertTrue(openDisposed.get()); + assertTrue(closeDisposed.get()); + } } 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 bfccb343da..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.*; @@ -32,7 +32,6 @@ import io.reactivex.schedulers.*; import io.reactivex.subjects.*; - public class ObservableWindowWithTimeTest { private TestScheduler scheduler; @@ -65,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 @@ -158,6 +159,7 @@ public void onNext(T args) { } }; } + @Test public void testExactWindowSize() { Observable> source = Observable.range(1, 10) @@ -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/ObservableWithLatestFromTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromTest.java index 9b1ea3e54e..713772c535 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromTest.java @@ -134,7 +134,6 @@ public void testEmptyOther() { assertFalse(other.hasObservers()); } - @Test public void testUnsubscription() { PublishSubject source = PublishSubject.create(); @@ -189,6 +188,7 @@ public void testSourceThrows() { assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); } + @Test public void testOtherThrows() { PublishSubject source = PublishSubject.create(); @@ -260,7 +260,6 @@ public void testNoDownstreamUnsubscribe() { // assertTrue("Not cancelled!", ts.isCancelled()); } - static final Function toArray = new Function() { @Override public String apply(Object[] args) { 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 ac68979af9..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.*; @@ -1261,6 +1260,7 @@ public Object apply(Integer a, Integer b, Integer c, Integer d, Integer e, Integ .test() .assertResult("12345678"); } + @Test public void zip9() { Observable.zip(Observable.just(1), @@ -1350,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() { @@ -1427,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/SingleAmbTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleAmbTest.java index 1bc00dedd6..18f4f3be65 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleAmbTest.java @@ -16,14 +16,18 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.BiConsumer; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.*; public class SingleAmbTest { @@ -280,4 +284,61 @@ public void ambArrayOrder() { Single error = Single.error(new RuntimeException()); Single.ambArray(Single.just(1), error).test().assertValue(1); } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerSuccessDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Single.ambArray( + Single.just(1) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Single.never() + ) + .subscribe(new BiConsumer() { + @Override + public void accept(Object v, Throwable e) throws Exception { + assertNotNull(v); + assertNull(e); + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerErrorDispose() throws Exception { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Single.ambArray( + Single.error(ex) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Single.never() + ) + .subscribe(new BiConsumer() { + @Override + public void accept(Object v, Throwable e) throws Exception { + assertNull(v); + assertNotNull(e); + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleConcatTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleConcatTest.java index 52d67e742a..09a29e55ff 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleConcatTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleConcatTest.java @@ -163,7 +163,6 @@ public void subscribe(SingleEmitter s) throws Exception { assertEquals(1, calls[0]); } - @SuppressWarnings("unchecked") @Test public void noSubsequentSubscriptionIterable() { diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleDematerializeTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleDematerializeTest.java new file mode 100644 index 0000000000..abfbe2151a --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/single/SingleDematerializeTest.java @@ -0,0 +1,107 @@ +/** + * 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.single; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; +import io.reactivex.subjects.SingleSubject; + +public class SingleDematerializeTest { + + @Test + public void success() { + Single.just(Notification.createOnNext(1)) + .dematerialize(Functions.>identity()) + .test() + .assertResult(1); + } + + @Test + public void empty() { + Single.just(Notification.createOnComplete()) + .dematerialize(Functions.>identity()) + .test() + .assertResult(); + } + + @Test + public void error() { + Single.>error(new TestException()) + .dematerialize(Functions.>identity()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void errorNotification() { + Single.just(Notification.createOnError(new TestException())) + .dematerialize(Functions.>identity()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeSingleToMaybe(new Function, MaybeSource>() { + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public MaybeSource apply(Single v) throws Exception { + return v.dematerialize((Function)Functions.identity()); + } + }); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(SingleSubject.>create().dematerialize(Functions.>identity())); + } + + @Test + public void selectorCrash() { + Single.just(Notification.createOnNext(1)) + .dematerialize(new Function, Notification>() { + @Override + public Notification apply(Notification v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void selectorNull() { + Single.just(Notification.createOnNext(1)) + .dematerialize(Functions.justFunction((Notification)null)) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void selectorDifferentType() { + Single.just(Notification.createOnNext(1)) + .dematerialize(new Function, Notification>() { + @Override + public Notification apply(Notification v) throws Exception { + return Notification.createOnNext("Value-" + 1); + } + }) + .test() + .assertResult("Value-1"); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTerminateTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTerminateTest.java new file mode 100644 index 0000000000..425ee84205 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTerminateTest.java @@ -0,0 +1,95 @@ +/** + * 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.single; + +import io.reactivex.Single; +import io.reactivex.TestHelper; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.observers.TestObserver; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertTrue; + +public class SingleDoOnTerminateTest { + + @Test(expected = NullPointerException.class) + public void doOnTerminate() { + Single.just(1).doOnTerminate(null); + } + + @Test + public void doOnTerminateSuccess() { + final AtomicBoolean atomicBoolean = new AtomicBoolean(); + + Single.just(1).doOnTerminate(new Action() { + @Override + public void run() throws Exception { + atomicBoolean.set(true); + } + }) + .test() + .assertResult(1); + + assertTrue(atomicBoolean.get()); + } + + @Test + public void doOnTerminateError() { + final AtomicBoolean atomicBoolean = new AtomicBoolean(); + Single.error(new TestException()).doOnTerminate(new Action() { + @Override + public void run() { + atomicBoolean.set(true); + } + }) + .test() + .assertFailure(TestException.class); + + assertTrue(atomicBoolean.get()); + } + + @Test + public void doOnTerminateSuccessCrash() { + Single.just(1).doOnTerminate(new Action() { + @Override + public void run() throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doOnTerminateErrorCrash() { + TestObserver to = Single.error(new TestException("Outer")).doOnTerminate(new Action() { + @Override + public void run() { + throw new TestException("Inner"); + } + }) + .test() + .assertFailure(CompositeException.class); + + List errors = TestHelper.compositeList(to.errors().get(0)); + + TestHelper.assertError(errors, 0, TestException.class, "Outer"); + TestHelper.assertError(errors, 1, TestException.class, "Inner"); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapTest.java index ee1de82fb7..ca586a0907 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapTest.java @@ -105,7 +105,6 @@ public Completable apply(Integer t) throws Exception { assertFalse(b[0]); } - @Test public void flatMapObservable() { Single.just(1).flatMapObservable(new Function>() { 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 ebd0445358..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 { @@ -111,7 +110,6 @@ public void shouldNotInvokeFuncUntilSubscription() throws Exception { verify(func).call(); } - @Test public void noErrorLoss() throws Exception { List errors = TestHelper.trackPluginErrors(); diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleInternalHelperTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleInternalHelperTest.java index 5d4459259b..0a6d5473f7 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleInternalHelperTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleInternalHelperTest.java @@ -21,7 +21,6 @@ import io.reactivex.*; - public class SingleInternalHelperTest { @Test diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleMaterializeTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleMaterializeTest.java new file mode 100644 index 0000000000..3a97155978 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/single/SingleMaterializeTest.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.operators.single; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.subjects.SingleSubject; + +public class SingleMaterializeTest { + + @Test + @SuppressWarnings("unchecked") + public void success() { + Single.just(1) + .materialize() + .test() + .assertResult(Notification.createOnNext(1)); + } + + @Test + @SuppressWarnings("unchecked") + public void error() { + TestException ex = new TestException(); + Maybe.error(ex) + .materialize() + .test() + .assertResult(Notification.createOnError(ex)); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeSingle(new Function, SingleSource>>() { + @Override + public SingleSource> apply(Single v) throws Exception { + return v.materialize(); + } + }); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(SingleSubject.create().materialize()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleMergeTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleMergeTest.java index bf1a100ac9..ef5f6de826 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleMergeTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleMergeTest.java @@ -125,7 +125,6 @@ public void mergeDelayError3() { .assertFailure(TestException.class, 1, 2); } - @Test public void mergeDelayError4() { Single.mergeDelayError( 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/operators/single/SingleTakeUntilTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleTakeUntilTest.java index d646542a93..c7a1180a30 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleTakeUntilTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleTakeUntilTest.java @@ -59,7 +59,6 @@ public void mainSuccessSingle() { to.assertResult(1); } - @Test public void mainSuccessCompletable() { PublishProcessor pp = PublishProcessor.create(); diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleTimeoutTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleTimeoutTest.java index 37077a6dd2..0acbc7a9ef 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleTimeoutTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleTimeoutTest.java @@ -13,10 +13,12 @@ package io.reactivex.internal.operators.single; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.junit.Test; @@ -209,4 +211,14 @@ public void run() { RxJavaPlugins.reset(); } } + + @Test + public void mainTimedOut() { + Single + .never() + .timeout(1, TimeUnit.NANOSECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailureAndMessage(TimeoutException.class, timeoutMessage(1, TimeUnit.NANOSECONDS)); + } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleZipArrayTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleZipArrayTest.java index 2f7e398c03..7d1175bfc2 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleZipArrayTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleZipArrayTest.java @@ -36,7 +36,6 @@ public Object apply(Object a, Object b) throws Exception { } }; - final Function3 addString3 = new Function3() { @Override public Object apply(Object a, Object b, Object c) throws Exception { @@ -150,6 +149,7 @@ public void run() { } } } + @SuppressWarnings("unchecked") @Test(expected = NullPointerException.class) public void zipArrayOneIsNull() { diff --git a/src/test/java/io/reactivex/internal/schedulers/AbstractDirectTaskTest.java b/src/test/java/io/reactivex/internal/schedulers/AbstractDirectTaskTest.java index 1357f27b5c..17a69ab93d 100644 --- a/src/test/java/io/reactivex/internal/schedulers/AbstractDirectTaskTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/AbstractDirectTaskTest.java @@ -115,6 +115,7 @@ public boolean cancel(boolean mayInterruptIfRunning) { assertTrue(interrupted[0]); } + @Test public void setFutureCancelSameThread() { AbstractDirectTask task = new AbstractDirectTask(Functions.EMPTY_RUNNABLE) { diff --git a/src/test/java/io/reactivex/internal/schedulers/ExecutorSchedulerDelayedRunnableTest.java b/src/test/java/io/reactivex/internal/schedulers/ExecutorSchedulerDelayedRunnableTest.java index 0356ed6cbf..a8f61daeab 100644 --- a/src/test/java/io/reactivex/internal/schedulers/ExecutorSchedulerDelayedRunnableTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/ExecutorSchedulerDelayedRunnableTest.java @@ -24,7 +24,6 @@ public class ExecutorSchedulerDelayedRunnableTest { - @Test(expected = TestException.class) public void delayedRunnableCrash() { DelayedRunnable dl = new DelayedRunnable(new Runnable() { diff --git a/src/test/java/io/reactivex/internal/schedulers/ImmediateThinSchedulerTest.java b/src/test/java/io/reactivex/internal/schedulers/ImmediateThinSchedulerTest.java index 747aa49bfe..507ebd32d0 100644 --- a/src/test/java/io/reactivex/internal/schedulers/ImmediateThinSchedulerTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/ImmediateThinSchedulerTest.java @@ -47,6 +47,7 @@ public void scheduleDirectTimed() { public void scheduleDirectPeriodic() { ImmediateThinScheduler.INSTANCE.schedulePeriodicallyDirect(Functions.EMPTY_RUNNABLE, 1, 1, TimeUnit.SECONDS); } + @Test public void schedule() { final int[] count = { 0 }; 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/ScheduledRunnableTest.java b/src/test/java/io/reactivex/internal/schedulers/ScheduledRunnableTest.java index add5de3ed0..9d1718f7d0 100644 --- a/src/test/java/io/reactivex/internal/schedulers/ScheduledRunnableTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/ScheduledRunnableTest.java @@ -372,7 +372,6 @@ public void asyncDisposeIdempotent() { assertEquals(ScheduledRunnable.ASYNC_DISPOSED, run.get(ScheduledRunnable.FUTURE_INDEX)); } - @Test public void noParentIsDisposed() { ScheduledRunnable run = new ScheduledRunnable(Functions.EMPTY_RUNNABLE, null); 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/subscribers/BlockingSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/BlockingSubscriberTest.java index 3c35e2315d..3d6017bad4 100644 --- a/src/test/java/io/reactivex/internal/subscribers/BlockingSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/BlockingSubscriberTest.java @@ -93,6 +93,7 @@ public void cancelOnRequest() { public void request(long n) { bf.cancelled = true; } + @Override public void cancel() { b.set(true); @@ -118,6 +119,7 @@ public void cancelUpfront() { public void request(long n) { b.set(true); } + @Override public void cancel() { } diff --git a/src/test/java/io/reactivex/internal/subscribers/DeferredScalarSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/DeferredScalarSubscriberTest.java index 8643924bad..726828bbd7 100644 --- a/src/test/java/io/reactivex/internal/subscribers/DeferredScalarSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/DeferredScalarSubscriberTest.java @@ -230,7 +230,6 @@ public void doubleComplete() { ds.onComplete(); ds.onComplete(); - ts.assertValue(1); ts.assertNoErrors(); ts.assertComplete(); @@ -303,6 +302,7 @@ public void callsAfterUnsubscribe() { ts.assertNoErrors(); ts.assertNotComplete(); } + @Test public void emissionRequestRace() { Worker w = Schedulers.computation().createWorker(); diff --git a/src/test/java/io/reactivex/internal/subscribers/FutureSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/FutureSubscriberTest.java index baef7a9176..2aa9ec7a25 100644 --- a/src/test/java/io/reactivex/internal/subscribers/FutureSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/FutureSubscriberTest.java @@ -13,6 +13,7 @@ package io.reactivex.internal.subscribers; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; import java.util.*; @@ -281,4 +282,14 @@ public void run() { assertEquals(1, fs.get().intValue()); } + + @Test + public void getTimedOut() throws Exception { + try { + fs.get(1, TimeUnit.NANOSECONDS); + fail("Should have thrown"); + } catch (TimeoutException expected) { + assertEquals(timeoutMessage(1, TimeUnit.NANOSECONDS), expected.getMessage()); + } + } } diff --git a/src/test/java/io/reactivex/internal/subscribers/InnerQueuedSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/InnerQueuedSubscriberTest.java index 28058606c4..f71ad289d0 100644 --- a/src/test/java/io/reactivex/internal/subscribers/InnerQueuedSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/InnerQueuedSubscriberTest.java @@ -27,12 +27,15 @@ public void requestInBatches() { @Override public void innerNext(InnerQueuedSubscriber inner, Integer value) { } + @Override public void innerError(InnerQueuedSubscriber inner, Throwable e) { } + @Override public void innerComplete(InnerQueuedSubscriber inner) { } + @Override public void drain() { } @@ -47,6 +50,7 @@ public void drain() { public void request(long n) { requests.add(n); } + @Override public void cancel() { // ignore diff --git a/src/test/java/io/reactivex/internal/subscribers/LambdaSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/LambdaSubscriberTest.java index 7f16d6e0ee..d61d1a4dfe 100644 --- a/src/test/java/io/reactivex/internal/subscribers/LambdaSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/LambdaSubscriberTest.java @@ -242,6 +242,7 @@ public void accept(Subscription s) throws Exception { assertEquals(Arrays.asList(1, 100), received); } + @Test public void badSourceEmitAfterDone() { Flowable source = Flowable.fromPublisher(new Publisher() { diff --git a/src/test/java/io/reactivex/internal/subscriptions/SubscriptionArbiterTest.java b/src/test/java/io/reactivex/internal/subscriptions/SubscriptionArbiterTest.java index 02d7d30c0a..426d1779e1 100644 --- a/src/test/java/io/reactivex/internal/subscriptions/SubscriptionArbiterTest.java +++ b/src/test/java/io/reactivex/internal/subscriptions/SubscriptionArbiterTest.java @@ -26,7 +26,7 @@ public class SubscriptionArbiterTest { @Test public void setSubscriptionMissed() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.getAndIncrement(); @@ -45,7 +45,7 @@ public void setSubscriptionMissed() { @Test public void invalidDeferredRequest() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); List errors = TestHelper.trackPluginErrors(); try { @@ -59,7 +59,7 @@ public void invalidDeferredRequest() { @Test public void unbounded() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.request(Long.MAX_VALUE); @@ -86,7 +86,7 @@ public void unbounded() { @Test public void cancelled() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.cancelled = true; BooleanSubscription bs1 = new BooleanSubscription(); @@ -102,7 +102,7 @@ public void cancelled() { @Test public void drainUnbounded() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.getAndIncrement(); @@ -113,7 +113,7 @@ public void drainUnbounded() { @Test public void drainMissedRequested() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.getAndIncrement(); @@ -128,7 +128,7 @@ public void drainMissedRequested() { @Test public void drainMissedRequestedProduced() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.getAndIncrement(); @@ -147,7 +147,7 @@ public void drainMissedRequestedProduced() { public void drainMissedRequestedMoreProduced() { List errors = TestHelper.trackPluginErrors(); try { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.getAndIncrement(); @@ -169,7 +169,7 @@ public void drainMissedRequestedMoreProduced() { @Test public void missedSubscriptionNoPrior() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.getAndIncrement(); @@ -181,4 +181,130 @@ public void missedSubscriptionNoPrior() { assertSame(bs1, sa.actual); } + + @Test + public void noCancelFastPath() { + SubscriptionArbiter sa = new SubscriptionArbiter(false); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + + sa.setSubscription(bs1); + sa.setSubscription(bs2); + + assertFalse(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + } + + @Test + public void cancelFastPath() { + SubscriptionArbiter sa = new SubscriptionArbiter(true); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + + sa.setSubscription(bs1); + sa.setSubscription(bs2); + + assertTrue(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + } + + @Test + public void noCancelSlowPathReplace() { + SubscriptionArbiter sa = new SubscriptionArbiter(false); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + BooleanSubscription bs3 = new BooleanSubscription(); + + sa.setSubscription(bs1); + + sa.getAndIncrement(); + + sa.setSubscription(bs2); + sa.setSubscription(bs3); + + sa.drainLoop(); + + assertFalse(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + assertFalse(bs3.isCancelled()); + } + + @Test + public void cancelSlowPathReplace() { + SubscriptionArbiter sa = new SubscriptionArbiter(true); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + BooleanSubscription bs3 = new BooleanSubscription(); + + sa.setSubscription(bs1); + + sa.getAndIncrement(); + + sa.setSubscription(bs2); + sa.setSubscription(bs3); + + sa.drainLoop(); + + assertTrue(bs1.isCancelled()); + assertTrue(bs2.isCancelled()); + assertFalse(bs3.isCancelled()); + } + + @Test + public void noCancelSlowPath() { + SubscriptionArbiter sa = new SubscriptionArbiter(false); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + + sa.setSubscription(bs1); + + sa.getAndIncrement(); + + sa.setSubscription(bs2); + + sa.drainLoop(); + + assertFalse(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + } + + @Test + public void cancelSlowPath() { + SubscriptionArbiter sa = new SubscriptionArbiter(true); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + + sa.setSubscription(bs1); + + sa.getAndIncrement(); + + sa.setSubscription(bs2); + + sa.drainLoop(); + + assertTrue(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + } + + @Test + public void moreProducedViolationFastPath() { + List errors = TestHelper.trackPluginErrors(); + try { + SubscriptionArbiter sa = new SubscriptionArbiter(true); + + sa.produced(2); + + assertEquals(0, sa.requested); + + TestHelper.assertError(errors, 0, IllegalStateException.class, "More produced than requested: -2"); + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/subscriptions/SubscriptionHelperTest.java b/src/test/java/io/reactivex/internal/subscriptions/SubscriptionHelperTest.java index f7314b1d68..dd010ca100 100644 --- a/src/test/java/io/reactivex/internal/subscriptions/SubscriptionHelperTest.java +++ b/src/test/java/io/reactivex/internal/subscriptions/SubscriptionHelperTest.java @@ -51,15 +51,15 @@ public void cancelNoOp() { @Test public void set() { - AtomicReference s = new AtomicReference(); + AtomicReference atomicSubscription = new AtomicReference(); BooleanSubscription bs1 = new BooleanSubscription(); - assertTrue(SubscriptionHelper.set(s, bs1)); + assertTrue(SubscriptionHelper.set(atomicSubscription, bs1)); BooleanSubscription bs2 = new BooleanSubscription(); - assertTrue(SubscriptionHelper.set(s, bs2)); + assertTrue(SubscriptionHelper.set(atomicSubscription, bs2)); assertTrue(bs1.isCancelled()); @@ -68,15 +68,15 @@ public void set() { @Test public void replace() { - AtomicReference s = new AtomicReference(); + AtomicReference atomicSubscription = new AtomicReference(); BooleanSubscription bs1 = new BooleanSubscription(); - assertTrue(SubscriptionHelper.replace(s, bs1)); + assertTrue(SubscriptionHelper.replace(atomicSubscription, bs1)); BooleanSubscription bs2 = new BooleanSubscription(); - assertTrue(SubscriptionHelper.replace(s, bs2)); + assertTrue(SubscriptionHelper.replace(atomicSubscription, bs2)); assertFalse(bs1.isCancelled()); @@ -86,12 +86,12 @@ public void replace() { @Test public void cancelRace() { for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { - final AtomicReference s = new AtomicReference(); + final AtomicReference atomicSubscription = new AtomicReference(); Runnable r = new Runnable() { @Override public void run() { - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(atomicSubscription); } }; @@ -102,7 +102,7 @@ public void run() { @Test public void setRace() { for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { - final AtomicReference s = new AtomicReference(); + final AtomicReference atomicSubscription = new AtomicReference(); final BooleanSubscription bs1 = new BooleanSubscription(); final BooleanSubscription bs2 = new BooleanSubscription(); @@ -110,14 +110,14 @@ public void setRace() { Runnable r1 = new Runnable() { @Override public void run() { - SubscriptionHelper.set(s, bs1); + SubscriptionHelper.set(atomicSubscription, bs1); } }; Runnable r2 = new Runnable() { @Override public void run() { - SubscriptionHelper.set(s, bs2); + SubscriptionHelper.set(atomicSubscription, bs2); } }; @@ -130,7 +130,7 @@ public void run() { @Test public void replaceRace() { for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { - final AtomicReference s = new AtomicReference(); + final AtomicReference atomicSubscription = new AtomicReference(); final BooleanSubscription bs1 = new BooleanSubscription(); final BooleanSubscription bs2 = new BooleanSubscription(); @@ -138,14 +138,14 @@ public void replaceRace() { Runnable r1 = new Runnable() { @Override public void run() { - SubscriptionHelper.replace(s, bs1); + SubscriptionHelper.replace(atomicSubscription, bs1); } }; Runnable r2 = new Runnable() { @Override public void run() { - SubscriptionHelper.replace(s, bs2); + SubscriptionHelper.replace(atomicSubscription, bs2); } }; @@ -158,31 +158,31 @@ public void run() { @Test public void cancelAndChange() { - AtomicReference s = new AtomicReference(); + AtomicReference atomicSubscription = new AtomicReference(); - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(atomicSubscription); BooleanSubscription bs1 = new BooleanSubscription(); - assertFalse(SubscriptionHelper.set(s, bs1)); + assertFalse(SubscriptionHelper.set(atomicSubscription, bs1)); assertTrue(bs1.isCancelled()); - assertFalse(SubscriptionHelper.set(s, null)); + assertFalse(SubscriptionHelper.set(atomicSubscription, null)); BooleanSubscription bs2 = new BooleanSubscription(); - assertFalse(SubscriptionHelper.replace(s, bs2)); + assertFalse(SubscriptionHelper.replace(atomicSubscription, bs2)); assertTrue(bs2.isCancelled()); - assertFalse(SubscriptionHelper.replace(s, null)); + assertFalse(SubscriptionHelper.replace(atomicSubscription, null)); } @Test public void invalidDeferredRequest() { - AtomicReference s = new AtomicReference(); + AtomicReference atomicSubscription = new AtomicReference(); AtomicLong r = new AtomicLong(); List errors = TestHelper.trackPluginErrors(); try { - SubscriptionHelper.deferredRequest(s, r, -99); + SubscriptionHelper.deferredRequest(atomicSubscription, r, -99); TestHelper.assertError(errors, 0, IllegalArgumentException.class, "n > 0 required but it was -99"); } finally { @@ -193,7 +193,7 @@ public void invalidDeferredRequest() { @Test public void deferredRace() { for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { - final AtomicReference s = new AtomicReference(); + final AtomicReference atomicSubscription = new AtomicReference(); final AtomicLong r = new AtomicLong(); final AtomicLong q = new AtomicLong(); @@ -213,20 +213,20 @@ public void cancel() { Runnable r1 = new Runnable() { @Override public void run() { - SubscriptionHelper.deferredSetOnce(s, r, a); + SubscriptionHelper.deferredSetOnce(atomicSubscription, r, a); } }; Runnable r2 = new Runnable() { @Override public void run() { - SubscriptionHelper.deferredRequest(s, r, 1); + SubscriptionHelper.deferredRequest(atomicSubscription, r, 1); } }; TestHelper.race(r1, r2); - assertSame(a, s.get()); + assertSame(a, atomicSubscription.get()); assertEquals(1, q.get()); assertEquals(0, r.get()); } diff --git a/src/test/java/io/reactivex/internal/util/EndConsumerHelperTest.java b/src/test/java/io/reactivex/internal/util/EndConsumerHelperTest.java index 9fbddd4279..13a672f566 100644 --- a/src/test/java/io/reactivex/internal/util/EndConsumerHelperTest.java +++ b/src/test/java/io/reactivex/internal/util/EndConsumerHelperTest.java @@ -54,9 +54,11 @@ public void checkDoubleDefaultSubscriber() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -85,9 +87,11 @@ static final class EndDefaultSubscriber extends DefaultSubscriber { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -124,9 +128,11 @@ public void checkDoubleDisposableSubscriber() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -157,9 +163,11 @@ public void checkDoubleResourceSubscriber() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -190,9 +198,11 @@ public void checkDoubleDefaultObserver() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -223,9 +233,11 @@ public void checkDoubleDisposableObserver() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -256,9 +268,11 @@ public void checkDoubleResourceObserver() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -289,6 +303,7 @@ public void checkDoubleDisposableSingleObserver() { @Override public void onSuccess(Integer t) { } + @Override public void onError(Throwable t) { } @@ -319,6 +334,7 @@ public void checkDoubleResourceSingleObserver() { @Override public void onSuccess(Integer t) { } + @Override public void onError(Throwable t) { } @@ -349,9 +365,11 @@ public void checkDoubleDisposableMaybeObserver() { @Override public void onSuccess(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -382,9 +400,11 @@ public void checkDoubleResourceMaybeObserver() { @Override public void onSuccess(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -415,6 +435,7 @@ public void checkDoubleDisposableCompletableObserver() { @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -445,6 +466,7 @@ public void checkDoubleResourceCompletableObserver() { @Override public void onError(Throwable t) { } + @Override public void onComplete() { } 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/QueueDrainHelperTest.java b/src/test/java/io/reactivex/internal/util/QueueDrainHelperTest.java index 14375b506a..6be3075191 100644 --- a/src/test/java/io/reactivex/internal/util/QueueDrainHelperTest.java +++ b/src/test/java/io/reactivex/internal/util/QueueDrainHelperTest.java @@ -814,6 +814,7 @@ public void accept(Observer a, Integer v) { to.assertFailure(TestException.class); } + @Test public void observerCheckTerminatedNonDelayErrorErrorResource() { TestObserver to = new TestObserver(); 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/MaybeCreateTest.java b/src/test/java/io/reactivex/maybe/MaybeCreateTest.java index e13b5f09cb..90689133d5 100644 --- a/src/test/java/io/reactivex/maybe/MaybeCreateTest.java +++ b/src/test/java/io/reactivex/maybe/MaybeCreateTest.java @@ -15,12 +15,15 @@ import static org.junit.Assert.assertTrue; +import java.util.List; + import org.junit.Test; import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Cancellable; +import io.reactivex.plugins.RxJavaPlugins; public class MaybeCreateTest { @Test(expected = NullPointerException.class) @@ -30,85 +33,111 @@ public void nullArgument() { @Test public void basic() { - final Disposable d = Disposables.empty(); - - Maybe.create(new MaybeOnSubscribe() { - @Override - public void subscribe(MaybeEmitter e) throws Exception { - e.setDisposable(d); - - e.onSuccess(1); - e.onError(new TestException()); - e.onSuccess(2); - e.onError(new TestException()); - } - }).test().assertResult(1); - - assertTrue(d.isDisposed()); + List errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); + + Maybe.create(new MaybeOnSubscribe() { + @Override + public void subscribe(MaybeEmitter e) throws Exception { + e.setDisposable(d); + + e.onSuccess(1); + e.onError(new TestException()); + e.onSuccess(2); + e.onError(new TestException()); + } + }).test().assertResult(1); + + assertTrue(d.isDisposed()); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithCancellable() { - final Disposable d1 = Disposables.empty(); - final Disposable d2 = Disposables.empty(); - - Maybe.create(new MaybeOnSubscribe() { - @Override - public void subscribe(MaybeEmitter e) throws Exception { - e.setDisposable(d1); - e.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - d2.dispose(); - } - }); - - e.onSuccess(1); - e.onError(new TestException()); - e.onSuccess(2); - e.onError(new TestException()); - } - }).test().assertResult(1); - - assertTrue(d1.isDisposed()); - assertTrue(d2.isDisposed()); + List errors = TestHelper.trackPluginErrors(); + try { + final Disposable d1 = Disposables.empty(); + final Disposable d2 = Disposables.empty(); + + Maybe.create(new MaybeOnSubscribe() { + @Override + public void subscribe(MaybeEmitter e) throws Exception { + e.setDisposable(d1); + e.setCancellable(new Cancellable() { + @Override + public void cancel() throws Exception { + d2.dispose(); + } + }); + + e.onSuccess(1); + e.onError(new TestException()); + e.onSuccess(2); + e.onError(new TestException()); + } + }).test().assertResult(1); + + assertTrue(d1.isDisposed()); + assertTrue(d2.isDisposed()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithError() { - final Disposable d = Disposables.empty(); - - Maybe.create(new MaybeOnSubscribe() { - @Override - public void subscribe(MaybeEmitter e) throws Exception { - e.setDisposable(d); - - e.onError(new TestException()); - e.onSuccess(2); - e.onError(new TestException()); - } - }).test().assertFailure(TestException.class); - - assertTrue(d.isDisposed()); + List errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); + + Maybe.create(new MaybeOnSubscribe() { + @Override + public void subscribe(MaybeEmitter e) throws Exception { + e.setDisposable(d); + + e.onError(new TestException()); + e.onSuccess(2); + e.onError(new TestException()); + } + }).test().assertFailure(TestException.class); + + assertTrue(d.isDisposed()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } - @Test public void basicWithCompletion() { - final Disposable d = Disposables.empty(); - - Maybe.create(new MaybeOnSubscribe() { - @Override - public void subscribe(MaybeEmitter e) throws Exception { - e.setDisposable(d); - - e.onComplete(); - e.onSuccess(2); - e.onError(new TestException()); - } - }).test().assertComplete(); - - assertTrue(d.isDisposed()); + List errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); + + Maybe.create(new MaybeOnSubscribe() { + @Override + public void subscribe(MaybeEmitter e) throws Exception { + e.setDisposable(d); + + e.onComplete(); + e.onSuccess(2); + e.onError(new TestException()); + } + }).test().assertComplete(); + + assertTrue(d.isDisposed()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test(expected = IllegalArgumentException.class) diff --git a/src/test/java/io/reactivex/maybe/MaybeTest.java b/src/test/java/io/reactivex/maybe/MaybeTest.java index 94c7c9eb8a..45b31b0a8a 100644 --- a/src/test/java/io/reactivex/maybe/MaybeTest.java +++ b/src/test/java/io/reactivex/maybe/MaybeTest.java @@ -350,7 +350,6 @@ public void completableMaybeCompletable() { Completable.complete().toMaybe().ignoreElement().test().assertResult(); } - @Test public void unsafeCreate() { Maybe.unsafeCreate(new MaybeSource() { @@ -646,7 +645,6 @@ public void accept(Throwable e) throws Exception { assertNotEquals(main, name[0]); } - @Test public void observeOnCompleteThread() { String main = Thread.currentThread().getName(); @@ -688,7 +686,6 @@ public void subscribeOnComplete() { ; } - @Test public void fromAction() { final int[] call = { 0 }; @@ -801,7 +798,6 @@ public void accept(Integer v) throws Exception { .assertFailure(TestException.class); } - @Test public void doOnSubscribe() { final Disposable[] value = { null }; @@ -830,7 +826,6 @@ public void accept(Disposable v) throws Exception { .assertFailure(TestException.class); } - @Test public void doOnCompleteThrows() { Maybe.empty().doOnComplete(new Action() { @@ -862,7 +857,6 @@ public void run() throws Exception { assertEquals(1, call[0]); } - @Test public void doOnDisposeThrows() { List list = TestHelper.trackPluginErrors(); @@ -971,7 +965,6 @@ public void run() throws Exception { assertEquals(-1, call[0]); } - @Test public void doAfterTerminateComplete() { final int[] call = { 0 }; @@ -1013,7 +1006,6 @@ public void subscribe(MaybeObserver observer) { } } - @Test public void sourceThrowsIAE() { try { @@ -1180,6 +1172,7 @@ public void ignoreElementComplete() { .test() .assertResult(); } + @Test public void ignoreElementSuccessMaybe() { Maybe.just(1) @@ -1503,68 +1496,91 @@ public void nullArgument() { @Test public void basic() { - final Disposable d = Disposables.empty(); + List errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); - Maybe.create(new MaybeOnSubscribe() { - @Override - public void subscribe(MaybeEmitter e) throws Exception { - e.setDisposable(d); + Maybe.create(new MaybeOnSubscribe() { + @Override + public void subscribe(MaybeEmitter e) throws Exception { + e.setDisposable(d); + + e.onSuccess(1); + e.onError(new TestException()); + e.onSuccess(2); + e.onError(new TestException()); + e.onComplete(); + } + }) + .test() + .assertResult(1); - e.onSuccess(1); - e.onError(new TestException()); - e.onSuccess(2); - e.onError(new TestException()); - e.onComplete(); - } - }) - .test() - .assertResult(1); + assertTrue(d.isDisposed()); - assertTrue(d.isDisposed()); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithError() { - final Disposable d = Disposables.empty(); + List errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); - Maybe.create(new MaybeOnSubscribe() { - @Override - public void subscribe(MaybeEmitter e) throws Exception { - e.setDisposable(d); + Maybe.create(new MaybeOnSubscribe() { + @Override + public void subscribe(MaybeEmitter e) throws Exception { + e.setDisposable(d); - e.onError(new TestException()); - e.onSuccess(2); - e.onError(new TestException()); - e.onComplete(); - } - }) - .test() - .assertFailure(TestException.class); + e.onError(new TestException()); + e.onSuccess(2); + e.onError(new TestException()); + e.onComplete(); + } + }) + .test() + .assertFailure(TestException.class); - assertTrue(d.isDisposed()); + assertTrue(d.isDisposed()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithComplete() { - final Disposable d = Disposables.empty(); + List errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); - Maybe.create(new MaybeOnSubscribe() { - @Override - public void subscribe(MaybeEmitter e) throws Exception { - e.setDisposable(d); + Maybe.create(new MaybeOnSubscribe() { + @Override + public void subscribe(MaybeEmitter e) throws Exception { + e.setDisposable(d); + + e.onComplete(); + e.onSuccess(1); + e.onError(new TestException()); + e.onComplete(); + e.onSuccess(2); + e.onError(new TestException()); + } + }) + .test() + .assertResult(); - e.onComplete(); - e.onSuccess(1); - e.onError(new TestException()); - e.onComplete(); - e.onSuccess(2); - e.onError(new TestException()); - } - }) - .test() - .assertResult(); + assertTrue(d.isDisposed()); - assertTrue(d.isDisposed()); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test(expected = IllegalArgumentException.class) @@ -2366,21 +2382,28 @@ public void accept(Integer v, Throwable e) throws Exception { @Test public void doOnEventError() { - final List list = new ArrayList(); + List errors = TestHelper.trackPluginErrors(); + try { + final List list = new ArrayList(); - TestException ex = new TestException(); + TestException ex = new TestException(); - assertTrue(Maybe.error(ex) - .doOnEvent(new BiConsumer() { - @Override - public void accept(Integer v, Throwable e) throws Exception { - list.add(v); - list.add(e); - } - }) - .subscribe().isDisposed()); + assertTrue(Maybe.error(ex) + .doOnEvent(new BiConsumer() { + @Override + public void accept(Integer v, Throwable e) throws Exception { + list.add(v); + list.add(e); + } + }) + .subscribe().isDisposed()); + + assertEquals(Arrays.asList(null, ex), list); - assertEquals(Arrays.asList(null, ex), list); + TestHelper.assertError(errors, 0, OnErrorNotImplementedException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -2436,7 +2459,6 @@ public void accept(Integer v, Throwable e) throws Exception { assertEquals(2, list.size()); } - @Test public void doOnEventCompleteThrows() { Maybe.empty() @@ -2880,7 +2902,6 @@ public void zipArray() { .assertResult("[1]"); } - @SuppressWarnings("unchecked") @Test public void zipIterable() { @@ -2983,7 +3004,6 @@ public void zip9() { .assertResult("123456789"); } - @Test public void ambWith1SignalsSuccess() { PublishProcessor pp1 = PublishProcessor.create(); @@ -3165,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/ObservableNullTests.java b/src/test/java/io/reactivex/observable/ObservableNullTests.java index 7a429c4555..d4142b34af 100644 --- a/src/test/java/io/reactivex/observable/ObservableNullTests.java +++ b/src/test/java/io/reactivex/observable/ObservableNullTests.java @@ -546,7 +546,7 @@ public void intervalPeriodSchedulerNull() { @Test(expected = NullPointerException.class) public void intervalRangeUnitNull() { - Observable.intervalRange(1,1, 1, 1, null); + Observable.intervalRange(1, 1, 1, 1, null); } @Test(expected = NullPointerException.class) @@ -2768,7 +2768,6 @@ public Object apply(Integer a, Integer b) { }); } - @Test(expected = NullPointerException.class) public void zipWithCombinerNull() { just1.zipWith(just1, null); diff --git a/src/test/java/io/reactivex/observable/ObservableReduceTests.java b/src/test/java/io/reactivex/observable/ObservableReduceTests.java index 5a08fd2d65..a5bdacf48b 100644 --- a/src/test/java/io/reactivex/observable/ObservableReduceTests.java +++ b/src/test/java/io/reactivex/observable/ObservableReduceTests.java @@ -77,7 +77,6 @@ public Movie apply(Movie t1, Movie t2) { assertNotNull(reduceResult2); } - @Test public void reduceInts() { Observable o = Observable.just(1, 2, 3); diff --git a/src/test/java/io/reactivex/observable/ObservableSubscriberTest.java b/src/test/java/io/reactivex/observable/ObservableSubscriberTest.java index 14af3cf2d0..848d6128e1 100644 --- a/src/test/java/io/reactivex/observable/ObservableSubscriberTest.java +++ b/src/test/java/io/reactivex/observable/ObservableSubscriberTest.java @@ -180,7 +180,6 @@ public void safeSubscriberAlreadySafe() { to.assertResult(1); } - @Test public void methodTestNoCancel() { PublishSubject ps = PublishSubject.create(); @@ -206,7 +205,7 @@ public Observer apply(Observable a, Observer b) throws Exception { Observable.just(1).test(); fail("Should have thrown"); } catch (NullPointerException ex) { - assertEquals("Plugin returned null Observer", ex.getMessage()); + assertEquals("The RxJavaPlugins.onSubscribe hook returned a null Observer. Please change the handler provided to RxJavaPlugins.setOnObservableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins", ex.getMessage()); } } finally { RxJavaPlugins.reset(); diff --git a/src/test/java/io/reactivex/observable/ObservableTest.java b/src/test/java/io/reactivex/observable/ObservableTest.java index fbe071714b..f59a41857e 100644 --- a/src/test/java/io/reactivex/observable/ObservableTest.java +++ b/src/test/java/io/reactivex/observable/ObservableTest.java @@ -28,6 +28,7 @@ import io.reactivex.Observer; import io.reactivex.disposables.*; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.observables.ConnectableObservable; import io.reactivex.observers.*; import io.reactivex.schedulers.*; @@ -145,7 +146,6 @@ public Throwable call() { verify(w, times(1)).onError(any(RuntimeException.class)); } - @Test public void testCountAFewItems() { Observable o = Observable.just("a", "b", "c", "d"); @@ -358,7 +358,8 @@ public void testOnSubscribeFails() { @Test public void testMaterializeDematerializeChaining() { Observable obs = Observable.just(1); - Observable chained = obs.materialize().dematerialize(); + Observable chained = obs.materialize() + .dematerialize(Functions.>identity()); Observer observer = TestHelper.mockObserver(); @@ -1049,7 +1050,7 @@ public void testAmbWith() { public void testTakeWhileToList() { final int expectedCount = 3; final AtomicInteger count = new AtomicInteger(); - for (int i = 0;i < expectedCount; i++) { + for (int i = 0; i < expectedCount; i++) { Observable .just(Boolean.TRUE, Boolean.FALSE) .takeWhile(new Predicate() { @@ -1097,7 +1098,7 @@ public void testErrorThrownIssue1685() { Observable.error(new RuntimeException("oops")) .materialize() .delay(1, TimeUnit.SECONDS) - .dematerialize() + .dematerialize(Functions.>identity()) .subscribe(subject); subject.subscribe(); 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/observers/ObserverFusion.java b/src/test/java/io/reactivex/observers/ObserverFusion.java index 22b3466b19..05b694eba6 100644 --- a/src/test/java/io/reactivex/observers/ObserverFusion.java +++ b/src/test/java/io/reactivex/observers/ObserverFusion.java @@ -150,7 +150,6 @@ public static Consumer> assertFusionMode(final int mode) { return new AssertFusionConsumer(mode); } - /** * Constructs a TestObserver with the given required fusion mode. * @param the value type diff --git a/src/test/java/io/reactivex/observers/SafeObserverTest.java b/src/test/java/io/reactivex/observers/SafeObserverTest.java index 12c355905f..edd725b7ed 100644 --- a/src/test/java/io/reactivex/observers/SafeObserverTest.java +++ b/src/test/java/io/reactivex/observers/SafeObserverTest.java @@ -452,10 +452,12 @@ public void testOnCompletedThrows() { public void onNext(Integer t) { } + @Override public void onError(Throwable e) { error.set(e); } + @Override public void onComplete() { throw new TestException(); @@ -476,9 +478,11 @@ public void testActual() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable e) { } + @Override public void onComplete() { } diff --git a/src/test/java/io/reactivex/observers/SerializedObserverTest.java b/src/test/java/io/reactivex/observers/SerializedObserverTest.java index 726ca7aee4..f094ad7fc7 100644 --- a/src/test/java/io/reactivex/observers/SerializedObserverTest.java +++ b/src/test/java/io/reactivex/observers/SerializedObserverTest.java @@ -156,6 +156,7 @@ public void testMultiThreadedWithNPEinMiddle() { @Test public void runOutOfOrderConcurrencyTest() { ExecutorService tp = Executors.newFixedThreadPool(20); + List errors = TestHelper.trackPluginErrors(); try { TestConcurrencySubscriber tw = new TestConcurrencySubscriber(); // we need Synchronized + SafeObserver to handle synchronization plus life-cycle @@ -190,6 +191,10 @@ public void runOutOfOrderConcurrencyTest() { @SuppressWarnings("unused") int numNextEvents = tw.assertEvents(null); // no check of type since we don't want to test barging results here, just interleaving behavior // System.out.println("Number of events executed: " + numNextEvents); + + for (int i = 0; i < errors.size(); i++) { + TestHelper.assertUndeliverable(errors, i, RuntimeException.class); + } } catch (Throwable e) { fail("Concurrency test failed: " + e.getMessage()); e.printStackTrace(); @@ -200,6 +205,8 @@ public void runOutOfOrderConcurrencyTest() { } catch (InterruptedException e) { e.printStackTrace(); } + + RxJavaPlugins.reset(); } } @@ -955,25 +962,33 @@ public void onNext(Integer t) { @Test public void testErrorReentry() { - final AtomicReference> serial = new AtomicReference>(); + List errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference> serial = new AtomicReference>(); - TestObserver to = new TestObserver() { - @Override - public void onNext(Integer v) { - serial.get().onError(new TestException()); - serial.get().onError(new TestException()); - super.onNext(v); - } - }; - SerializedObserver sobs = new SerializedObserver(to); - sobs.onSubscribe(Disposables.empty()); - serial.set(sobs); + TestObserver to = new TestObserver() { + @Override + public void onNext(Integer v) { + serial.get().onError(new TestException()); + serial.get().onError(new TestException()); + super.onNext(v); + } + }; + SerializedObserver sobs = new SerializedObserver(to); + sobs.onSubscribe(Disposables.empty()); + serial.set(sobs); - sobs.onNext(1); + sobs.onNext(1); - to.assertValue(1); - to.assertError(TestException.class); + to.assertValue(1); + to.assertError(TestException.class); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } + @Test public void testCompleteReentry() { final AtomicReference> serial = new AtomicReference>(); diff --git a/src/test/java/io/reactivex/observers/TestObserverTest.java b/src/test/java/io/reactivex/observers/TestObserverTest.java index 1b638ace38..d2d81dc77c 100644 --- a/src/test/java/io/reactivex/observers/TestObserverTest.java +++ b/src/test/java/io/reactivex/observers/TestObserverTest.java @@ -272,6 +272,7 @@ public void testTerminalErrorOnce() { } fail("Failed to report multiple onError terminal events!"); } + @Test public void testTerminalCompletedOnce() { TestSubscriber ts = new TestSubscriber(); @@ -484,8 +485,6 @@ public boolean test(Throwable t) throws Exception { to.assertValueCount(0); to.assertNoValues(); - - } @Test @@ -897,7 +896,6 @@ public void assertTerminated2() { // expected } - to = TestObserver.create(); to.onSubscribe(Disposables.empty()); @@ -1409,7 +1407,7 @@ public void assertValueAtIndexNoMatch() { Observable.just("a", "b", "c").subscribe(to); thrown.expect(AssertionError.class); - thrown.expectMessage("Expected: b (class: String), Actual: c (class: String) (latch = 0, values = 3, errors = 0, completions = 1)"); + thrown.expectMessage("expected: b (class: String) but was: c (class: String) (latch = 0, values = 3, errors = 0, completions = 1)"); to.assertValueAt(2, "b"); } @@ -1625,4 +1623,38 @@ public void assertValueSequenceOnlyThrowsWhenErrored() { // expected } } + + @Test + public void assertValueSetWiderSet() { + Set set = new HashSet(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7)); + + Observable.just(4, 5, 1, 3, 2) + .test() + .assertValueSet(set); + } + + @Test + public void assertValueSetExact() { + Set set = new HashSet(Arrays.asList(1, 2, 3, 4, 5)); + + Observable.just(4, 5, 1, 3, 2) + .test() + .assertValueSet(set) + .assertValueCount(set.size()); + } + + @Test + public void assertValueSetMissing() { + Set set = new HashSet(Arrays.asList(0, 1, 2, 4, 5, 6, 7)); + + try { + Observable.range(1, 5) + .test() + .assertValueSet(set); + + throw new RuntimeException("Should have failed"); + } catch (AssertionError ex) { + assertTrue(ex.getMessage(), ex.getMessage().contains("Value not in the expected collection: " + 3)); + } + } } diff --git a/src/test/java/io/reactivex/parallel/ParallelDoOnNextTryTest.java b/src/test/java/io/reactivex/parallel/ParallelDoOnNextTryTest.java index 3a40edc229..41f82363d6 100644 --- a/src/test/java/io/reactivex/parallel/ParallelDoOnNextTryTest.java +++ b/src/test/java/io/reactivex/parallel/ParallelDoOnNextTryTest.java @@ -49,6 +49,7 @@ public void doOnNextNoError() { calls = 0; } } + @Test public void doOnNextErrorNoError() { for (ParallelFailureHandling e : ParallelFailureHandling.values()) { diff --git a/src/test/java/io/reactivex/parallel/ParallelFilterTryTest.java b/src/test/java/io/reactivex/parallel/ParallelFilterTryTest.java index bb7d919ce0..e49090acf8 100644 --- a/src/test/java/io/reactivex/parallel/ParallelFilterTryTest.java +++ b/src/test/java/io/reactivex/parallel/ParallelFilterTryTest.java @@ -94,6 +94,7 @@ public void filterConditionalNoError() { .assertResult(1); } } + @Test public void filterErrorConditionalNoError() { for (ParallelFailureHandling e : ParallelFailureHandling.values()) { diff --git a/src/test/java/io/reactivex/parallel/ParallelFlowableTest.java b/src/test/java/io/reactivex/parallel/ParallelFlowableTest.java index 577d4be1cd..ab98b5a9f4 100644 --- a/src/test/java/io/reactivex/parallel/ParallelFlowableTest.java +++ b/src/test/java/io/reactivex/parallel/ParallelFlowableTest.java @@ -454,7 +454,6 @@ public void accept(List v) throws Exception { } } - @Test public void collectAsync2() { ExecutorService exec = Executors.newFixedThreadPool(3); @@ -551,7 +550,6 @@ public void accept(List v) throws Exception { } } - @Test public void collectAsync3Fused() { ExecutorService exec = Executors.newFixedThreadPool(3); diff --git a/src/test/java/io/reactivex/parallel/ParallelMapTryTest.java b/src/test/java/io/reactivex/parallel/ParallelMapTryTest.java index bed3f5eae8..09b3dbcf6d 100644 --- a/src/test/java/io/reactivex/parallel/ParallelMapTryTest.java +++ b/src/test/java/io/reactivex/parallel/ParallelMapTryTest.java @@ -44,6 +44,7 @@ public void mapNoError() { .assertResult(1); } } + @Test public void mapErrorNoError() { for (ParallelFailureHandling e : ParallelFailureHandling.values()) { @@ -68,6 +69,7 @@ public void mapConditionalNoError() { .assertResult(1); } } + @Test public void mapErrorConditionalNoError() { for (ParallelFailureHandling e : ParallelFailureHandling.values()) { diff --git a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java index a8c9668731..229dc7c873 100644 --- a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java +++ b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java @@ -269,7 +269,7 @@ public boolean getAsBoolean() throws Exception { fail("Should have thrown InvocationTargetException(IllegalStateException)"); } catch (InvocationTargetException ex) { if (ex.getCause() instanceof IllegalStateException) { - assertEquals("Plugins can't be changed anymore",ex.getCause().getMessage()); + assertEquals("Plugins can't be changed anymore", ex.getCause().getMessage()); } else { fail("Should have thrown InvocationTargetException(IllegalStateException)"); } @@ -1285,7 +1285,6 @@ public Completable apply(Completable completable) throws Exception { } }; - RxJavaPlugins.setInitComputationSchedulerHandler(callable2scheduler); RxJavaPlugins.setComputationSchedulerHandler(scheduler2scheduler); RxJavaPlugins.setIoSchedulerHandler(scheduler2scheduler); @@ -1378,7 +1377,6 @@ public void subscribeActual(MaybeObserver t) { assertSame(myb, RxJavaPlugins.onAssembly(myb)); - Runnable action = Functions.EMPTY_RUNNABLE; assertSame(action, RxJavaPlugins.onSchedule(action)); @@ -1954,7 +1952,6 @@ public Subscriber apply(Flowable f, Subscriber s) throws Exception { } } - @SuppressWarnings("rawtypes") @Test public void maybeCreate() { @@ -2073,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 c97f3398e1..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; @@ -296,7 +295,6 @@ public void run() { // assertEquals(1, ts.getOnErrorEvents().size()); // } - // FIXME subscriber methods are not allowed to throw // /** // * This one has multiple failures so should get a CompositeException @@ -368,6 +366,7 @@ public void testCurrentStateMethodsEmpty() { assertNull(as.getValue()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { AsyncProcessor as = AsyncProcessor.create(); diff --git a/src/test/java/io/reactivex/processors/BehaviorProcessorTest.java b/src/test/java/io/reactivex/processors/BehaviorProcessorTest.java index 59d8cbff02..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; @@ -282,6 +281,7 @@ public void onComplete() { verify(subscriber, never()).onError(any(Throwable.class)); } } + @Test public void testStartEmpty() { BehaviorProcessor source = BehaviorProcessor.create(); @@ -304,9 +304,8 @@ public void testStartEmpty() { inOrder.verify(subscriber).onNext(1); inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); - - } + @Test public void testStartEmptyThenAddOne() { BehaviorProcessor source = BehaviorProcessor.create(); @@ -329,6 +328,7 @@ public void testStartEmptyThenAddOne() { verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void testStartEmptyCompleteWithOne() { BehaviorProcessor source = BehaviorProcessor.create(); @@ -406,6 +406,7 @@ public void testTakeOneSubscriber() { // // even though the onError above throws we should still receive it on the other subscriber // assertEquals(1, ts.getOnErrorEvents().size()); // } + @Test public void testEmissionSubscriptionRace() throws Exception { Scheduler s = Schedulers.io(); @@ -550,6 +551,7 @@ public void testCurrentStateMethodsEmpty() { assertNull(as.getValue()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { BehaviorProcessor as = BehaviorProcessor.create(); diff --git a/src/test/java/io/reactivex/processors/MulticastProcessorTest.java b/src/test/java/io/reactivex/processors/MulticastProcessorTest.java index c85665eacd..d541dffb3e 100644 --- a/src/test/java/io/reactivex/processors/MulticastProcessorTest.java +++ b/src/test/java/io/reactivex/processors/MulticastProcessorTest.java @@ -185,7 +185,6 @@ public void longRunning() { mp.test().assertValueCount(1000).assertNoErrors().assertComplete(); } - @Test public void oneByOne() { MulticastProcessor mp = MulticastProcessor.create(16); @@ -419,7 +418,6 @@ public void onNextNull() { mp.onNext(null); } - @Test(expected = NullPointerException.class) public void onOfferNull() { MulticastProcessor mp = MulticastProcessor.create(4, false); @@ -623,7 +621,6 @@ public void cancelUpfront() { assertFalse(mp.hasSubscribers()); } - @Test public void cancelUpfrontOtherConsumersPresent() { MulticastProcessor mp = MulticastProcessor.create(); @@ -786,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 8d9d4b7a88..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; @@ -338,7 +337,6 @@ public void onComplete() { } } - // FIXME RS subscribers are not allowed to throw // @Test // public void testOnErrorThrowsDoesntPreventDelivery() { @@ -384,6 +382,7 @@ public void onComplete() { // // even though the onError above throws we should still receive it on the other subscriber // assertEquals(1, ts.getOnErrorEvents().size()); // } + @Test public void testCurrentStateMethodsNormal() { PublishProcessor as = PublishProcessor.create(); @@ -419,6 +418,7 @@ public void testCurrentStateMethodsEmpty() { assertTrue(as.hasComplete()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { PublishProcessor as = PublishProcessor.create(); diff --git a/src/test/java/io/reactivex/processors/ReplayProcessorBoundedConcurrencyTest.java b/src/test/java/io/reactivex/processors/ReplayProcessorBoundedConcurrencyTest.java index dd0f32821f..0258488d52 100644 --- a/src/test/java/io/reactivex/processors/ReplayProcessorBoundedConcurrencyTest.java +++ b/src/test/java/io/reactivex/processors/ReplayProcessorBoundedConcurrencyTest.java @@ -284,6 +284,7 @@ public void run() { } /** + * Make sure emission-subscription races are handled correctly. * https://github.com/ReactiveX/RxJava/issues/1147 */ @Test @@ -318,6 +319,7 @@ public void run() { } } } + @Test public void testReplaySubjectEmissionSubscriptionRace() throws Exception { Scheduler s = Schedulers.io(); @@ -403,6 +405,7 @@ public void run() { worker.dispose(); } } + @Test(timeout = 5000) public void testConcurrentSizeAndHasAnyValue() throws InterruptedException { final ReplayProcessor rs = ReplayProcessor.createUnbounded(); @@ -457,6 +460,7 @@ public void run() { t.join(); } + @Test(timeout = 5000) public void testConcurrentSizeAndHasAnyValueBounded() throws InterruptedException { final ReplayProcessor rs = ReplayProcessor.createWithSize(3); @@ -500,6 +504,7 @@ public void run() { t.join(); } + @Test(timeout = 10000) public void testConcurrentSizeAndHasAnyValueTimeBounded() throws InterruptedException { final ReplayProcessor rs = ReplayProcessor.createWithTime(1, TimeUnit.MILLISECONDS, Schedulers.computation()); diff --git a/src/test/java/io/reactivex/processors/ReplayProcessorConcurrencyTest.java b/src/test/java/io/reactivex/processors/ReplayProcessorConcurrencyTest.java index e63f8f361f..978c86ebe4 100644 --- a/src/test/java/io/reactivex/processors/ReplayProcessorConcurrencyTest.java +++ b/src/test/java/io/reactivex/processors/ReplayProcessorConcurrencyTest.java @@ -284,6 +284,7 @@ public void run() { } /** + * Make sure emission-subscription races are handled correctly. * https://github.com/ReactiveX/RxJava/issues/1147 */ @Test @@ -318,6 +319,7 @@ public void run() { } } } + @Test public void testReplaySubjectEmissionSubscriptionRace() throws Exception { Scheduler s = Schedulers.io(); @@ -391,6 +393,7 @@ public void run() { worker.dispose(); } } + @Test(timeout = 10000) public void testConcurrentSizeAndHasAnyValue() throws InterruptedException { final ReplayProcessor rs = ReplayProcessor.create(); diff --git a/src/test/java/io/reactivex/processors/ReplayProcessorTest.java b/src/test/java/io/reactivex/processors/ReplayProcessorTest.java index fc5635d975..b59d20fb94 100644 --- a/src/test/java/io/reactivex/processors/ReplayProcessorTest.java +++ b/src/test/java/io/reactivex/processors/ReplayProcessorTest.java @@ -14,21 +14,21 @@ package io.reactivex.processors; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.lang.management.*; import java.util.Arrays; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.*; -import org.junit.Test; +import org.junit.*; import org.mockito.*; import org.reactivestreams.*; import io.reactivex.*; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.TestException; -import io.reactivex.functions.Function; +import io.reactivex.functions.*; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.processors.ReplayProcessor.*; import io.reactivex.schedulers.*; @@ -350,6 +350,7 @@ public void onNext(String v) { assertEquals("three", lastValueForSubscriber2.get()); } + @Test public void testSubscriptionLeak() { ReplayProcessor replaySubject = ReplayProcessor.create(); @@ -403,6 +404,7 @@ public void onComplete() { verify(subscriber, never()).onError(any(Throwable.class)); } } + @Test public void testTerminateOnce() { ReplayProcessor source = ReplayProcessor.create(); @@ -455,6 +457,7 @@ public void testReplay1AfterTermination() { verify(subscriber, never()).onError(any(Throwable.class)); } } + @Test public void testReplay1Directly() { ReplayProcessor source = ReplayProcessor.createWithSize(1); @@ -618,6 +621,7 @@ public void testCurrentStateMethodsEmpty() { assertTrue(as.hasComplete()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { ReplayProcessor as = ReplayProcessor.create(); @@ -632,6 +636,7 @@ public void testCurrentStateMethodsError() { assertFalse(as.hasComplete()); assertTrue(as.getThrowable() instanceof TestException); } + @Test public void testSizeAndHasAnyValueUnbounded() { ReplayProcessor rs = ReplayProcessor.create(); @@ -654,6 +659,7 @@ public void testSizeAndHasAnyValueUnbounded() { assertEquals(2, rs.size()); assertTrue(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnbounded() { ReplayProcessor rs = ReplayProcessor.createUnbounded(); @@ -699,6 +705,7 @@ public void testSizeAndHasAnyValueUnboundedError() { assertEquals(2, rs.size()); assertTrue(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnboundedError() { ReplayProcessor rs = ReplayProcessor.createUnbounded(); @@ -731,6 +738,7 @@ public void testSizeAndHasAnyValueUnboundedEmptyError() { assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnboundedEmptyError() { ReplayProcessor rs = ReplayProcessor.createUnbounded(); @@ -750,6 +758,7 @@ public void testSizeAndHasAnyValueUnboundedEmptyCompleted() { assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnboundedEmptyCompleted() { ReplayProcessor rs = ReplayProcessor.createUnbounded(); @@ -802,6 +811,7 @@ public void testSizeAndHasAnyValueTimeBounded() { assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } + @Test public void testGetValues() { ReplayProcessor rs = ReplayProcessor.create(); @@ -816,6 +826,7 @@ public void testGetValues() { assertArrayEquals(expected, rs.getValues()); } + @Test public void testGetValuesUnbounded() { ReplayProcessor rs = ReplayProcessor.createUnbounded(); @@ -848,7 +859,6 @@ public void testBackpressureHonored() { ts.assertNotComplete(); ts.assertNoErrors(); - ts.request(1); ts.assertValues(1, 2); ts.assertNotComplete(); @@ -877,7 +887,6 @@ public void testBackpressureHonoredSizeBound() { ts.assertNotComplete(); ts.assertNoErrors(); - ts.request(1); ts.assertValues(1, 2); ts.assertNotComplete(); @@ -906,7 +915,6 @@ public void testBackpressureHonoredTimeBound() { ts.assertNotComplete(); ts.assertNoErrors(); - ts.request(1); ts.assertValues(1, 2); ts.assertNotComplete(); @@ -1042,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]); } @@ -1533,6 +1541,7 @@ public void timeBoundCancelAfterOne() { source.subscribeWith(take1AndCancel()) .assertResult(1); } + @Test public void timeAndSizeBoundCancelAfterOne() { ReplayProcessor source = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.MINUTES, Schedulers.single(), 16); @@ -1683,4 +1692,137 @@ public void noHeadRetentionTime() { public void invalidRequest() { TestHelper.assertBadRequestReported(ReplayProcessor.create()); } + + @Test + public void noBoundedRetentionViaThreadLocal() throws Exception { + final ReplayProcessor rp = ReplayProcessor.createWithSize(1); + + Flowable source = rp.take(1) + .concatMap(new Function>() { + @Override + public Publisher apply(byte[] v) throws Exception { + return rp; + } + }) + .takeLast(1) + ; + + System.out.println("Bounded Replay Leak check: Wait before GC"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC"); + System.gc(); + + Thread.sleep(500); + + final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); + long initial = memHeap.getUsed(); + + System.out.printf("Bounded Replay Leak check: Starting: %.3f MB%n", initial / 1024.0 / 1024.0); + + final AtomicLong after = new AtomicLong(); + + source.subscribe(new Consumer() { + @Override + public void accept(byte[] v) throws Exception { + System.out.println("Bounded Replay Leak check: Wait before GC 2"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC 2"); + System.gc(); + + Thread.sleep(500); + + after.set(memoryMXBean.getHeapMemoryUsage().getUsed()); + } + }); + + for (int i = 0; i < 200; i++) { + rp.onNext(new byte[1024 * 1024]); + } + rp.onComplete(); + + System.out.printf("Bounded Replay Leak check: After: %.3f MB%n", after.get() / 1024.0 / 1024.0); + + if (initial + 100 * 1024 * 1024 < after.get()) { + Assert.fail("Bounded Replay Leak check: Memory leak detected: " + (initial / 1024.0 / 1024.0) + + " -> " + 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/SerializedProcessorTest.java b/src/test/java/io/reactivex/processors/SerializedProcessorTest.java index efbfe02a69..9d5b51c152 100644 --- a/src/test/java/io/reactivex/processors/SerializedProcessorTest.java +++ b/src/test/java/io/reactivex/processors/SerializedProcessorTest.java @@ -121,6 +121,7 @@ public void testPublishSubjectValueEmpty() { assertFalse(serial.hasThrowable()); assertNull(serial.getThrowable()); } + @Test public void testPublishSubjectValueError() { PublishProcessor async = PublishProcessor.create(); @@ -248,6 +249,7 @@ public void testReplaySubjectValueRelay() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayIncomplete() { ReplayProcessor async = ReplayProcessor.create(); @@ -265,6 +267,7 @@ public void testReplaySubjectValueRelayIncomplete() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayBounded() { ReplayProcessor async = ReplayProcessor.createWithSize(1); @@ -284,6 +287,7 @@ public void testReplaySubjectValueRelayBounded() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayBoundedIncomplete() { ReplayProcessor async = ReplayProcessor.createWithSize(1); @@ -302,6 +306,7 @@ public void testReplaySubjectValueRelayBoundedIncomplete() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayBoundedEmptyIncomplete() { ReplayProcessor async = ReplayProcessor.createWithSize(1); @@ -318,6 +323,7 @@ public void testReplaySubjectValueRelayBoundedEmptyIncomplete() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayEmptyIncomplete() { ReplayProcessor async = ReplayProcessor.create(); @@ -352,6 +358,7 @@ public void testReplaySubjectEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectError() { ReplayProcessor async = ReplayProcessor.create(); @@ -388,6 +395,7 @@ public void testReplaySubjectBoundedEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectBoundedError() { ReplayProcessor async = ReplayProcessor.createWithSize(1); diff --git a/src/test/java/io/reactivex/processors/UnicastProcessorTest.java b/src/test/java/io/reactivex/processors/UnicastProcessorTest.java index fdd8d4e2a9..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 { @@ -110,7 +114,7 @@ public void threeArgsFactory() { public void run() { } }; - UnicastProcessor ap = UnicastProcessor.create(16, noop,false); + UnicastProcessor ap = UnicastProcessor.create(16, noop, false); ap.onNext(1); ap.onError(new RuntimeException()); @@ -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) @@ -373,7 +377,6 @@ public void hasObservers() { public void drainFusedFailFast() { UnicastProcessor us = UnicastProcessor.create(false); - TestSubscriber ts = us.to(SubscriberFusion.test(1, QueueFuseable.ANY, false)); us.done = true; @@ -386,7 +389,6 @@ public void drainFusedFailFast() { public void drainFusedFailFastEmpty() { UnicastProcessor us = UnicastProcessor.create(false); - TestSubscriber ts = us.to(SubscriberFusion.test(1, QueueFuseable.ANY, false)); us.drainFused(ts); @@ -440,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 120dee09d7..af489f5650 100644 --- a/src/test/java/io/reactivex/schedulers/AbstractSchedulerConcurrencyTests.java +++ b/src/test/java/io/reactivex/schedulers/AbstractSchedulerConcurrencyTests.java @@ -36,6 +36,7 @@ public abstract class AbstractSchedulerConcurrencyTests extends AbstractSchedulerTests { /** + * Make sure canceling through {@code subscribeOn} works. * Bug report: https://github.com/ReactiveX/RxJava/issues/431 * @throws InterruptedException if the test is interrupted */ @@ -404,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/AbstractSchedulerTests.java b/src/test/java/io/reactivex/schedulers/AbstractSchedulerTests.java index ed67bce571..0c9755fd60 100644 --- a/src/test/java/io/reactivex/schedulers/AbstractSchedulerTests.java +++ b/src/test/java/io/reactivex/schedulers/AbstractSchedulerTests.java @@ -707,7 +707,6 @@ public void unwrapDefaultPeriodicTask() throws InterruptedException { return; } - final CountDownLatch cdl = new CountDownLatch(1); Runnable countDownRunnable = new Runnable() { @Override diff --git a/src/test/java/io/reactivex/schedulers/ComputationSchedulerTests.java b/src/test/java/io/reactivex/schedulers/ComputationSchedulerTests.java index 9caa79e7b3..86ae8ac54e 100644 --- a/src/test/java/io/reactivex/schedulers/ComputationSchedulerTests.java +++ b/src/test/java/io/reactivex/schedulers/ComputationSchedulerTests.java @@ -111,7 +111,6 @@ public void accept(String t) { }); } - @Test public final void testMergeWithExecutorScheduler() { @@ -123,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/ExecutorSchedulerInterruptibleTest.java b/src/test/java/io/reactivex/schedulers/ExecutorSchedulerInterruptibleTest.java new file mode 100644 index 0000000000..bccca7524e --- /dev/null +++ b/src/test/java/io/reactivex/schedulers/ExecutorSchedulerInterruptibleTest.java @@ -0,0 +1,664 @@ +/** + * 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.schedulers; + +import static org.junit.Assert.*; + +import java.lang.management.*; +import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.junit.*; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.schedulers.*; +import io.reactivex.plugins.RxJavaPlugins; + +public class ExecutorSchedulerInterruptibleTest extends AbstractSchedulerConcurrencyTests { + + static final Executor executor = Executors.newFixedThreadPool(2, new RxThreadFactory("TestCustomPool")); + + @Override + protected Scheduler getScheduler() { + return Schedulers.from(executor, true); + } + + @Test + @Ignore("Unhandled errors are no longer thrown") + public final void testUnhandledErrorIsDeliveredToThreadHandler() throws InterruptedException { + SchedulerTestHelper.testUnhandledErrorIsDeliveredToThreadHandler(getScheduler()); + } + + @Test + public final void testHandledErrorIsNotDeliveredToThreadHandler() throws InterruptedException { + SchedulerTestHelper.testHandledErrorIsNotDeliveredToThreadHandler(getScheduler()); + } + + public static void testCancelledRetention(Scheduler.Worker w, boolean periodic) throws InterruptedException { + System.out.println("Wait before GC"); + Thread.sleep(1000); + + System.out.println("GC"); + System.gc(); + + Thread.sleep(1000); + + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); + long initial = memHeap.getUsed(); + + System.out.printf("Starting: %.3f MB%n", initial / 1024.0 / 1024.0); + + int n = 100 * 1000; + if (periodic) { + final CountDownLatch cdl = new CountDownLatch(n); + final Runnable action = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + for (int i = 0; i < n; i++) { + if (i % 50000 == 0) { + System.out.println(" -> still scheduling: " + i); + } + w.schedulePeriodically(action, 0, 1, TimeUnit.DAYS); + } + + System.out.println("Waiting for the first round to finish..."); + cdl.await(); + } else { + for (int i = 0; i < n; i++) { + if (i % 50000 == 0) { + System.out.println(" -> still scheduling: " + i); + } + w.schedule(Functions.EMPTY_RUNNABLE, 1, TimeUnit.DAYS); + } + } + + memHeap = memoryMXBean.getHeapMemoryUsage(); + long after = memHeap.getUsed(); + System.out.printf("Peak: %.3f MB%n", after / 1024.0 / 1024.0); + + w.dispose(); + + System.out.println("Wait before second GC"); + System.out.println("JDK 6 purge is N log N because it removes and shifts one by one"); + int t = (int)(n * Math.log(n) / 100) + SchedulerPoolFactory.PURGE_PERIOD_SECONDS * 1000; + while (t > 0) { + System.out.printf(" >> Waiting for purge: %.2f s remaining%n", t / 1000d); + + System.gc(); + + Thread.sleep(1000); + + t -= 1000; + memHeap = memoryMXBean.getHeapMemoryUsage(); + long finish = memHeap.getUsed(); + System.out.printf("After: %.3f MB%n", finish / 1024.0 / 1024.0); + if (finish <= initial * 5) { + break; + } + } + + System.out.println("Second GC"); + System.gc(); + + Thread.sleep(1000); + + memHeap = memoryMXBean.getHeapMemoryUsage(); + long finish = memHeap.getUsed(); + System.out.printf("After: %.3f MB%n", finish / 1024.0 / 1024.0); + + if (finish > initial * 5) { + fail(String.format("Tasks retained: %.3f -> %.3f -> %.3f", initial / 1024 / 1024.0, after / 1024 / 1024.0, finish / 1024 / 1024d)); + } + } + + @Test(timeout = 60000) + public void testCancelledTaskRetention() throws InterruptedException { + ExecutorService exec = Executors.newSingleThreadExecutor(); + Scheduler s = Schedulers.from(exec, true); + try { + Scheduler.Worker w = s.createWorker(); + try { + testCancelledRetention(w, false); + } finally { + w.dispose(); + } + + w = s.createWorker(); + try { + testCancelledRetention(w, true); + } finally { + w.dispose(); + } + } finally { + exec.shutdownNow(); + } + } + + /** A simple executor which queues tasks and executes them one-by-one if executeOne() is called. */ + static final class TestExecutor implements Executor { + final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); + @Override + public void execute(Runnable command) { + queue.offer(command); + } + public void executeOne() { + Runnable r = queue.poll(); + if (r != null) { + r.run(); + } + } + public void executeAll() { + Runnable r; + while ((r = queue.poll()) != null) { + r.run(); + } + } + } + + @Test + public void testCancelledTasksDontRun() { + final AtomicInteger calls = new AtomicInteger(); + Runnable task = new Runnable() { + @Override + public void run() { + calls.getAndIncrement(); + } + }; + TestExecutor exec = new TestExecutor(); + Scheduler custom = Schedulers.from(exec, true); + Worker w = custom.createWorker(); + try { + Disposable d1 = w.schedule(task); + Disposable d2 = w.schedule(task); + Disposable d3 = w.schedule(task); + + d1.dispose(); + d2.dispose(); + d3.dispose(); + + exec.executeAll(); + + assertEquals(0, calls.get()); + } finally { + w.dispose(); + } + } + + @Test + public void testCancelledWorkerDoesntRunTasks() { + final AtomicInteger calls = new AtomicInteger(); + Runnable task = new Runnable() { + @Override + public void run() { + calls.getAndIncrement(); + } + }; + TestExecutor exec = new TestExecutor(); + Scheduler custom = Schedulers.from(exec, true); + Worker w = custom.createWorker(); + try { + w.schedule(task); + w.schedule(task); + w.schedule(task); + } finally { + w.dispose(); + } + exec.executeAll(); + assertEquals(0, calls.get()); + } + + // FIXME the internal structure changed and these can't be tested +// +// @Test +// public void testNoTimedTaskAfterScheduleRetention() throws InterruptedException { +// Executor e = new Executor() { +// @Override +// public void execute(Runnable command) { +// command.run(); +// } +// }; +// ExecutorWorker w = (ExecutorWorker)Schedulers.from(e, true).createWorker(); +// +// w.schedule(Functions.emptyRunnable(), 50, TimeUnit.MILLISECONDS); +// +// assertTrue(w.tasks.hasSubscriptions()); +// +// Thread.sleep(150); +// +// assertFalse(w.tasks.hasSubscriptions()); +// } +// +// @Test +// public void testNoTimedTaskPartRetention() { +// Executor e = new Executor() { +// @Override +// public void execute(Runnable command) { +// +// } +// }; +// ExecutorWorker w = (ExecutorWorker)Schedulers.from(e, true).createWorker(); +// +// Disposable task = w.schedule(Functions.emptyRunnable(), 1, TimeUnit.DAYS); +// +// assertTrue(w.tasks.hasSubscriptions()); +// +// task.dispose(); +// +// assertFalse(w.tasks.hasSubscriptions()); +// } +// +// @Test +// public void testNoPeriodicTimedTaskPartRetention() throws InterruptedException { +// Executor e = new Executor() { +// @Override +// public void execute(Runnable command) { +// command.run(); +// } +// }; +// ExecutorWorker w = (ExecutorWorker)Schedulers.from(e, true).createWorker(); +// +// final CountDownLatch cdl = new CountDownLatch(1); +// final Runnable action = new Runnable() { +// @Override +// public void run() { +// cdl.countDown(); +// } +// }; +// +// Disposable task = w.schedulePeriodically(action, 0, 1, TimeUnit.DAYS); +// +// assertTrue(w.tasks.hasSubscriptions()); +// +// cdl.await(); +// +// task.dispose(); +// +// assertFalse(w.tasks.hasSubscriptions()); +// } + + @Test + public void plainExecutor() throws Exception { + Scheduler s = Schedulers.from(new Executor() { + @Override + public void execute(Runnable r) { + r.run(); + } + }, true); + + final CountDownLatch cdl = new CountDownLatch(5); + + Runnable r = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + + s.scheduleDirect(r); + + s.scheduleDirect(r, 50, TimeUnit.MILLISECONDS); + + Disposable d = s.schedulePeriodicallyDirect(r, 10, 10, TimeUnit.MILLISECONDS); + + try { + assertTrue(cdl.await(5, TimeUnit.SECONDS)); + } finally { + d.dispose(); + } + + assertTrue(d.isDisposed()); + } + + @Test + public void rejectingExecutor() { + ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); + exec.shutdown(); + + Scheduler s = Schedulers.from(exec, true); + + List errors = TestHelper.trackPluginErrors(); + + try { + assertSame(EmptyDisposable.INSTANCE, s.scheduleDirect(Functions.EMPTY_RUNNABLE)); + + assertSame(EmptyDisposable.INSTANCE, s.scheduleDirect(Functions.EMPTY_RUNNABLE, 10, TimeUnit.MILLISECONDS)); + + assertSame(EmptyDisposable.INSTANCE, s.schedulePeriodicallyDirect(Functions.EMPTY_RUNNABLE, 10, 10, TimeUnit.MILLISECONDS)); + + TestHelper.assertUndeliverable(errors, 0, RejectedExecutionException.class); + TestHelper.assertUndeliverable(errors, 1, RejectedExecutionException.class); + TestHelper.assertUndeliverable(errors, 2, RejectedExecutionException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void rejectingExecutorWorker() { + ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); + exec.shutdown(); + + List errors = TestHelper.trackPluginErrors(); + + try { + Worker s = Schedulers.from(exec, true).createWorker(); + assertSame(EmptyDisposable.INSTANCE, s.schedule(Functions.EMPTY_RUNNABLE)); + + s = Schedulers.from(exec, true).createWorker(); + assertSame(EmptyDisposable.INSTANCE, s.schedule(Functions.EMPTY_RUNNABLE, 10, TimeUnit.MILLISECONDS)); + + s = Schedulers.from(exec, true).createWorker(); + assertSame(EmptyDisposable.INSTANCE, s.schedulePeriodically(Functions.EMPTY_RUNNABLE, 10, 10, TimeUnit.MILLISECONDS)); + + TestHelper.assertUndeliverable(errors, 0, RejectedExecutionException.class); + TestHelper.assertUndeliverable(errors, 1, RejectedExecutionException.class); + TestHelper.assertUndeliverable(errors, 2, RejectedExecutionException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void reuseScheduledExecutor() throws Exception { + ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); + + try { + Scheduler s = Schedulers.from(exec, true); + + final CountDownLatch cdl = new CountDownLatch(8); + + Runnable r = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + + s.scheduleDirect(r); + + s.scheduleDirect(r, 10, TimeUnit.MILLISECONDS); + + Disposable d = s.schedulePeriodicallyDirect(r, 10, 10, TimeUnit.MILLISECONDS); + + try { + assertTrue(cdl.await(5, TimeUnit.SECONDS)); + } finally { + d.dispose(); + } + } finally { + exec.shutdown(); + } + } + + @Test + public void reuseScheduledExecutorAsWorker() throws Exception { + ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); + + Worker s = Schedulers.from(exec, true).createWorker(); + + assertFalse(s.isDisposed()); + try { + + final CountDownLatch cdl = new CountDownLatch(8); + + Runnable r = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + + s.schedule(r); + + s.schedule(r, 10, TimeUnit.MILLISECONDS); + + Disposable d = s.schedulePeriodically(r, 10, 10, TimeUnit.MILLISECONDS); + + try { + assertTrue(cdl.await(5, TimeUnit.SECONDS)); + } finally { + d.dispose(); + } + } finally { + s.dispose(); + exec.shutdown(); + } + + assertTrue(s.isDisposed()); + } + + @Test + public void disposeRace() { + ExecutorService exec = Executors.newSingleThreadExecutor(); + final Scheduler s = Schedulers.from(exec, true); + try { + for (int i = 0; i < 500; i++) { + final Worker w = s.createWorker(); + + final AtomicInteger c = new AtomicInteger(2); + + w.schedule(new Runnable() { + @Override + public void run() { + c.decrementAndGet(); + while (c.get() != 0) { } + } + }); + + c.decrementAndGet(); + while (c.get() != 0) { } + w.dispose(); + } + } finally { + exec.shutdownNow(); + } + } + + @Test + public void runnableDisposed() { + final Scheduler s = Schedulers.from(new Executor() { + @Override + public void execute(Runnable r) { + r.run(); + } + }, true); + Disposable d = s.scheduleDirect(Functions.EMPTY_RUNNABLE); + + assertTrue(d.isDisposed()); + } + + @Test(timeout = 1000) + public void runnableDisposedAsync() throws Exception { + final Scheduler s = Schedulers.from(new Executor() { + @Override + public void execute(Runnable r) { + new Thread(r).start(); + } + }, true); + Disposable d = s.scheduleDirect(Functions.EMPTY_RUNNABLE); + + while (!d.isDisposed()) { + Thread.sleep(1); + } + } + + @Test(timeout = 1000) + public void runnableDisposedAsync2() throws Exception { + final Scheduler s = Schedulers.from(executor, true); + Disposable d = s.scheduleDirect(Functions.EMPTY_RUNNABLE); + + while (!d.isDisposed()) { + Thread.sleep(1); + } + } + + @Test(timeout = 1000) + public void runnableDisposedAsyncCrash() throws Exception { + final Scheduler s = Schedulers.from(new Executor() { + @Override + public void execute(Runnable r) { + new Thread(r).start(); + } + }, true); + Disposable d = s.scheduleDirect(new Runnable() { + @Override + public void run() { + throw new IllegalStateException(); + } + }); + + while (!d.isDisposed()) { + Thread.sleep(1); + } + } + + @Test(timeout = 1000) + public void runnableDisposedAsyncTimed() throws Exception { + final Scheduler s = Schedulers.from(new Executor() { + @Override + public void execute(Runnable r) { + new Thread(r).start(); + } + }, true); + Disposable d = s.scheduleDirect(Functions.EMPTY_RUNNABLE, 1, TimeUnit.MILLISECONDS); + + while (!d.isDisposed()) { + Thread.sleep(1); + } + } + + @Test(timeout = 1000) + public void runnableDisposedAsyncTimed2() throws Exception { + ExecutorService executorScheduler = Executors.newScheduledThreadPool(1, new RxThreadFactory("TestCustomPoolTimed")); + try { + final Scheduler s = Schedulers.from(executorScheduler, true); + Disposable d = s.scheduleDirect(Functions.EMPTY_RUNNABLE, 1, TimeUnit.MILLISECONDS); + + while (!d.isDisposed()) { + Thread.sleep(1); + } + } finally { + executorScheduler.shutdownNow(); + } + } + + @Test + public void unwrapScheduleDirectTaskAfterDispose() { + Scheduler scheduler = getScheduler(); + final CountDownLatch cdl = new CountDownLatch(1); + Runnable countDownRunnable = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + Disposable disposable = scheduler.scheduleDirect(countDownRunnable, 100, TimeUnit.MILLISECONDS); + SchedulerRunnableIntrospection wrapper = (SchedulerRunnableIntrospection) disposable; + assertSame(countDownRunnable, wrapper.getWrappedRunnable()); + disposable.dispose(); + + assertSame(Functions.EMPTY_RUNNABLE, wrapper.getWrappedRunnable()); + } + + @Test(timeout = 10000) + public void interruptibleDirectTask() throws Exception { + Scheduler scheduler = getScheduler(); + + final AtomicInteger sync = new AtomicInteger(2); + + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + Disposable d = scheduler.scheduleDirect(new Runnable() { + @Override + public void run() { + if (sync.decrementAndGet() != 0) { + while (sync.get() != 0) { } + } + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + } + }); + + if (sync.decrementAndGet() != 0) { + while (sync.get() != 0) { } + } + + Thread.sleep(500); + + d.dispose(); + + int i = 20; + while (i-- > 0 && !isInterrupted.get()) { + Thread.sleep(50); + } + + assertTrue("Interruption did not propagate", isInterrupted.get()); + } + + @Test(timeout = 10000) + public void interruptibleWorkerTask() throws Exception { + Scheduler scheduler = getScheduler(); + + Worker worker = scheduler.createWorker(); + + try { + final AtomicInteger sync = new AtomicInteger(2); + + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + Disposable d = worker.schedule(new Runnable() { + @Override + public void run() { + if (sync.decrementAndGet() != 0) { + while (sync.get() != 0) { } + } + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + } + }); + + if (sync.decrementAndGet() != 0) { + while (sync.get() != 0) { } + } + + Thread.sleep(500); + + d.dispose(); + + int i = 20; + while (i-- > 0 && !isInterrupted.get()) { + Thread.sleep(50); + } + + assertTrue("Interruption did not propagate", isInterrupted.get()); + } finally { + worker.dispose(); + } + } +} diff --git a/src/test/java/io/reactivex/schedulers/ExecutorSchedulerTest.java b/src/test/java/io/reactivex/schedulers/ExecutorSchedulerTest.java index 954981a91e..eaa5b692f1 100644 --- a/src/test/java/io/reactivex/schedulers/ExecutorSchedulerTest.java +++ b/src/test/java/io/reactivex/schedulers/ExecutorSchedulerTest.java @@ -59,7 +59,6 @@ public static void testCancelledRetention(Scheduler.Worker w, boolean periodic) Thread.sleep(1000); - MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); long initial = memHeap.getUsed(); @@ -204,6 +203,7 @@ public void run() { w.dispose(); } } + @Test public void testCancelledWorkerDoesntRunTasks() { final AtomicInteger calls = new AtomicInteger(); diff --git a/src/test/java/io/reactivex/schedulers/SchedulerLifecycleTest.java b/src/test/java/io/reactivex/schedulers/SchedulerLifecycleTest.java index 7f3098ef90..aa0af9816b 100644 --- a/src/test/java/io/reactivex/schedulers/SchedulerLifecycleTest.java +++ b/src/test/java/io/reactivex/schedulers/SchedulerLifecycleTest.java @@ -92,7 +92,6 @@ public void run() { cd.add(w4); w4.schedule(countAction); - if (!cdl.await(3, TimeUnit.SECONDS)) { fail("countAction was not run by every worker"); } diff --git a/src/test/java/io/reactivex/schedulers/SchedulerTest.java b/src/test/java/io/reactivex/schedulers/SchedulerTest.java index bee0b3935b..99ffca6afb 100644 --- a/src/test/java/io/reactivex/schedulers/SchedulerTest.java +++ b/src/test/java/io/reactivex/schedulers/SchedulerTest.java @@ -204,7 +204,6 @@ public void run() { } - @Test public void periodicDirectTaskRaceIO() throws Exception { final Scheduler scheduler = Schedulers.io(); diff --git a/src/test/java/io/reactivex/schedulers/TestSchedulerTest.java b/src/test/java/io/reactivex/schedulers/TestSchedulerTest.java index 2d5484cbb3..4139365384 100644 --- a/src/test/java/io/reactivex/schedulers/TestSchedulerTest.java +++ b/src/test/java/io/reactivex/schedulers/TestSchedulerTest.java @@ -232,9 +232,9 @@ public void timedRunnableToString() { TimedRunnable r = new TimedRunnable((TestWorker) new TestScheduler().createWorker(), 5, new Runnable() { @Override public void run() { - // TODO Auto-generated method stub - + // deliberately no-op } + @Override public String toString() { return "Runnable"; 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 3efcbbcc6e..059f106ffe 100644 --- a/src/test/java/io/reactivex/single/SingleNullTests.java +++ b/src/test/java/io/reactivex/single/SingleNullTests.java @@ -13,7 +13,6 @@ package io.reactivex.single; - import java.lang.reflect.*; import java.util.*; import java.util.concurrent.*; @@ -23,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; @@ -809,6 +807,7 @@ public void subscribeOnErrorNull() { public void accept(Integer v) { } }, null); } + @Test(expected = NullPointerException.class) public void subscribeSubscriberNull() { just1.toFlowable().subscribe((Subscriber)null); diff --git a/src/test/java/io/reactivex/subjects/AsyncSubjectTest.java b/src/test/java/io/reactivex/subjects/AsyncSubjectTest.java index 9376962b95..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; @@ -295,7 +294,6 @@ public void run() { // assertEquals(1, to.getOnErrorEvents().size()); // } - // FIXME subscriber methods are not allowed to throw // /** // * This one has multiple failures so should get a CompositeException @@ -367,6 +365,7 @@ public void testCurrentStateMethodsEmpty() { assertNull(as.getValue()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { AsyncSubject as = AsyncSubject.create(); @@ -386,7 +385,6 @@ public void testCurrentStateMethodsError() { assertTrue(as.getThrowable() instanceof TestException); } - @Test public void fusionLive() { AsyncSubject ap = new AsyncSubject(); diff --git a/src/test/java/io/reactivex/subjects/BehaviorSubjectTest.java b/src/test/java/io/reactivex/subjects/BehaviorSubjectTest.java index f8044d5a11..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; @@ -282,6 +281,7 @@ public void onComplete() { verify(o, never()).onError(any(Throwable.class)); } } + @Test public void testStartEmpty() { BehaviorSubject source = BehaviorSubject.create(); @@ -304,9 +304,8 @@ public void testStartEmpty() { inOrder.verify(o).onNext(1); inOrder.verify(o).onComplete(); inOrder.verifyNoMoreInteractions(); - - } + @Test public void testStartEmptyThenAddOne() { BehaviorSubject source = BehaviorSubject.create(); @@ -329,6 +328,7 @@ public void testStartEmptyThenAddOne() { verify(o, never()).onError(any(Throwable.class)); } + @Test public void testStartEmptyCompleteWithOne() { BehaviorSubject source = BehaviorSubject.create(); @@ -406,6 +406,7 @@ public void testTakeOneSubscriber() { // // even though the onError above throws we should still receive it on the other subscriber // assertEquals(1, to.getOnErrorEvents().size()); // } + @Test public void testEmissionSubscriptionRace() throws Exception { Scheduler s = Schedulers.io(); @@ -550,6 +551,7 @@ public void testCurrentStateMethodsEmpty() { assertNull(as.getValue()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { BehaviorSubject as = BehaviorSubject.create(); @@ -716,7 +718,6 @@ public void onComplete() { }); } - @Test public void completeSubscribeRace() throws Exception { for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { diff --git a/src/test/java/io/reactivex/subjects/PublishSubjectTest.java b/src/test/java/io/reactivex/subjects/PublishSubjectTest.java index 12f4116814..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; @@ -338,7 +337,6 @@ public void onComplete() { } } - // FIXME RS subscribers are not allowed to throw // @Test // public void testOnErrorThrowsDoesntPreventDelivery() { @@ -384,6 +382,7 @@ public void onComplete() { // // even though the onError above throws we should still receive it on the other subscriber // assertEquals(1, to.getOnErrorEvents().size()); // } + @Test public void testCurrentStateMethodsNormal() { PublishSubject as = PublishSubject.create(); @@ -419,6 +418,7 @@ public void testCurrentStateMethodsEmpty() { assertTrue(as.hasComplete()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { PublishSubject as = PublishSubject.create(); diff --git a/src/test/java/io/reactivex/subjects/ReplaySubjectBoundedConcurrencyTest.java b/src/test/java/io/reactivex/subjects/ReplaySubjectBoundedConcurrencyTest.java index 4a03449597..693fd9a6ee 100644 --- a/src/test/java/io/reactivex/subjects/ReplaySubjectBoundedConcurrencyTest.java +++ b/src/test/java/io/reactivex/subjects/ReplaySubjectBoundedConcurrencyTest.java @@ -288,6 +288,7 @@ public void run() { } /** + * Make sure emission-subscription races are handled correctly. * https://github.com/ReactiveX/RxJava/issues/1147 */ @Test @@ -322,6 +323,7 @@ public void run() { } } } + @Test public void testReplaySubjectEmissionSubscriptionRace() throws Exception { Scheduler s = Schedulers.io(); @@ -407,6 +409,7 @@ public void run() { worker.dispose(); } } + @Test(timeout = 5000) public void testConcurrentSizeAndHasAnyValue() throws InterruptedException { final ReplaySubject rs = ReplaySubject.createUnbounded(); @@ -461,6 +464,7 @@ public void run() { t.join(); } + @Test(timeout = 5000) public void testConcurrentSizeAndHasAnyValueBounded() throws InterruptedException { final ReplaySubject rs = ReplaySubject.createWithSize(3); @@ -504,6 +508,7 @@ public void run() { t.join(); } + @Test(timeout = 10000) public void testConcurrentSizeAndHasAnyValueTimeBounded() throws InterruptedException { final ReplaySubject rs = ReplaySubject.createWithTime(1, TimeUnit.MILLISECONDS, Schedulers.computation()); diff --git a/src/test/java/io/reactivex/subjects/ReplaySubjectConcurrencyTest.java b/src/test/java/io/reactivex/subjects/ReplaySubjectConcurrencyTest.java index adf238118f..67120c49bc 100644 --- a/src/test/java/io/reactivex/subjects/ReplaySubjectConcurrencyTest.java +++ b/src/test/java/io/reactivex/subjects/ReplaySubjectConcurrencyTest.java @@ -288,6 +288,7 @@ public void run() { } /** + * Make sure emission-subscription races are handled correctly. * https://github.com/ReactiveX/RxJava/issues/1147 */ @Test @@ -322,6 +323,7 @@ public void run() { } } } + @Test public void testReplaySubjectEmissionSubscriptionRace() throws Exception { Scheduler s = Schedulers.io(); @@ -395,6 +397,7 @@ public void run() { worker.dispose(); } } + @Test(timeout = 10000) public void testConcurrentSizeAndHasAnyValue() throws InterruptedException { final ReplaySubject rs = ReplaySubject.create(); diff --git a/src/test/java/io/reactivex/subjects/ReplaySubjectTest.java b/src/test/java/io/reactivex/subjects/ReplaySubjectTest.java index 24d9704b8a..ce098ca16d 100644 --- a/src/test/java/io/reactivex/subjects/ReplaySubjectTest.java +++ b/src/test/java/io/reactivex/subjects/ReplaySubjectTest.java @@ -14,20 +14,20 @@ package io.reactivex.subjects; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.lang.management.*; import java.util.Arrays; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.*; -import org.junit.Test; +import org.junit.*; import org.mockito.*; import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; -import io.reactivex.functions.Function; +import io.reactivex.functions.*; import io.reactivex.observers.*; import io.reactivex.schedulers.*; import io.reactivex.subjects.ReplaySubject.*; @@ -348,6 +348,7 @@ public void onNext(String v) { assertEquals("three", lastValueForSubscriber2.get()); } + @Test public void testSubscriptionLeak() { ReplaySubject subject = ReplaySubject.create(); @@ -360,6 +361,7 @@ public void testSubscriptionLeak() { assertEquals(0, subject.observerCount()); } + @Test(timeout = 1000) public void testUnsubscriptionCase() { ReplaySubject src = ReplaySubject.create(); @@ -401,6 +403,7 @@ public void onComplete() { verify(o, never()).onError(any(Throwable.class)); } } + @Test public void testTerminateOnce() { ReplaySubject source = ReplaySubject.create(); @@ -453,6 +456,7 @@ public void testReplay1AfterTermination() { verify(o, never()).onError(any(Throwable.class)); } } + @Test public void testReplay1Directly() { ReplaySubject source = ReplaySubject.createWithSize(1); @@ -616,6 +620,7 @@ public void testCurrentStateMethodsEmpty() { assertTrue(as.hasComplete()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { ReplaySubject as = ReplaySubject.create(); @@ -630,6 +635,7 @@ public void testCurrentStateMethodsError() { assertFalse(as.hasComplete()); assertTrue(as.getThrowable() instanceof TestException); } + @Test public void testSizeAndHasAnyValueUnbounded() { ReplaySubject rs = ReplaySubject.create(); @@ -652,6 +658,7 @@ public void testSizeAndHasAnyValueUnbounded() { assertEquals(2, rs.size()); assertTrue(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnbounded() { ReplaySubject rs = ReplaySubject.createUnbounded(); @@ -697,6 +704,7 @@ public void testSizeAndHasAnyValueUnboundedError() { assertEquals(2, rs.size()); assertTrue(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnboundedError() { ReplaySubject rs = ReplaySubject.createUnbounded(); @@ -729,6 +737,7 @@ public void testSizeAndHasAnyValueUnboundedEmptyError() { assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnboundedEmptyError() { ReplaySubject rs = ReplaySubject.createUnbounded(); @@ -748,6 +757,7 @@ public void testSizeAndHasAnyValueUnboundedEmptyCompleted() { assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnboundedEmptyCompleted() { ReplaySubject rs = ReplaySubject.createUnbounded(); @@ -800,6 +810,7 @@ public void testSizeAndHasAnyValueTimeBounded() { assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } + @Test public void testGetValues() { ReplaySubject rs = ReplaySubject.create(); @@ -814,6 +825,7 @@ public void testGetValues() { assertArrayEquals(expected, rs.getValues()); } + @Test public void testGetValuesUnbounded() { ReplaySubject rs = ReplaySubject.createUnbounded(); @@ -952,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]); } @@ -1205,7 +1217,6 @@ public void noHeadRetentionCompleteSize() { assertSame(o, buf.head); } - @Test public void noHeadRetentionSize() { ReplaySubject source = ReplaySubject.createWithSize(1); @@ -1273,4 +1284,137 @@ public void noHeadRetentionTime() { assertSame(o, buf.head); } + + @Test + public void noBoundedRetentionViaThreadLocal() throws Exception { + final ReplaySubject rs = ReplaySubject.createWithSize(1); + + Observable source = rs.take(1) + .concatMap(new Function>() { + @Override + public Observable apply(byte[] v) throws Exception { + return rs; + } + }) + .takeLast(1) + ; + + System.out.println("Bounded Replay Leak check: Wait before GC"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC"); + System.gc(); + + Thread.sleep(500); + + final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); + long initial = memHeap.getUsed(); + + System.out.printf("Bounded Replay Leak check: Starting: %.3f MB%n", initial / 1024.0 / 1024.0); + + final AtomicLong after = new AtomicLong(); + + source.subscribe(new Consumer() { + @Override + public void accept(byte[] v) throws Exception { + System.out.println("Bounded Replay Leak check: Wait before GC 2"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC 2"); + System.gc(); + + Thread.sleep(500); + + after.set(memoryMXBean.getHeapMemoryUsage().getUsed()); + } + }); + + for (int i = 0; i < 200; i++) { + rs.onNext(new byte[1024 * 1024]); + } + rs.onComplete(); + + System.out.printf("Bounded Replay Leak check: After: %.3f MB%n", after.get() / 1024.0 / 1024.0); + + if (initial + 100 * 1024 * 1024 < after.get()) { + Assert.fail("Bounded Replay Leak check: Memory leak detected: " + (initial / 1024.0 / 1024.0) + + " -> " + 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/SerializedSubjectTest.java b/src/test/java/io/reactivex/subjects/SerializedSubjectTest.java index 31498d7a04..8d679b7024 100644 --- a/src/test/java/io/reactivex/subjects/SerializedSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/SerializedSubjectTest.java @@ -122,6 +122,7 @@ public void testPublishSubjectValueEmpty() { assertFalse(serial.hasThrowable()); assertNull(serial.getThrowable()); } + @Test public void testPublishSubjectValueError() { PublishSubject async = PublishSubject.create(); @@ -267,6 +268,7 @@ public void testReplaySubjectValueRelayIncomplete() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayBounded() { ReplaySubject async = ReplaySubject.createWithSize(1); @@ -286,6 +288,7 @@ public void testReplaySubjectValueRelayBounded() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayBoundedIncomplete() { ReplaySubject async = ReplaySubject.createWithSize(1); @@ -304,6 +307,7 @@ public void testReplaySubjectValueRelayBoundedIncomplete() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayBoundedEmptyIncomplete() { ReplaySubject async = ReplaySubject.createWithSize(1); @@ -320,6 +324,7 @@ public void testReplaySubjectValueRelayBoundedEmptyIncomplete() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayEmptyIncomplete() { ReplaySubject async = ReplaySubject.create(); @@ -354,6 +359,7 @@ public void testReplaySubjectEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectError() { ReplaySubject async = ReplaySubject.create(); @@ -390,6 +396,7 @@ public void testReplaySubjectBoundedEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectBoundedError() { ReplaySubject async = ReplaySubject.createWithSize(1); diff --git a/src/test/java/io/reactivex/subjects/UnicastSubjectTest.java b/src/test/java/io/reactivex/subjects/UnicastSubjectTest.java index 41a1d3e759..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) @@ -438,7 +441,6 @@ public void hasObservers() { public void drainFusedFailFast() { UnicastSubject us = UnicastSubject.create(false); - TestObserver to = us.to(ObserverFusion.test(QueueFuseable.ANY, false)); us.done = true; @@ -451,11 +453,43 @@ public void drainFusedFailFast() { public void drainFusedFailFastEmpty() { UnicastSubject us = UnicastSubject.create(false); - TestObserver to = us.to(ObserverFusion.test(QueueFuseable.ANY, false)); us.drainFused(to); 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/SafeSubscriberTest.java b/src/test/java/io/reactivex/subscribers/SafeSubscriberTest.java index d66a1ceecf..3ac4a19d7c 100644 --- a/src/test/java/io/reactivex/subscribers/SafeSubscriberTest.java +++ b/src/test/java/io/reactivex/subscribers/SafeSubscriberTest.java @@ -579,10 +579,12 @@ public void testOnCompletedThrows() { public void onNext(Integer t) { } + @Override public void onError(Throwable e) { error.set(e); } + @Override public void onComplete() { throw new TestException(); @@ -603,9 +605,11 @@ public void testActual() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable e) { } + @Override public void onComplete() { } @@ -1085,7 +1089,6 @@ public void cancelCrash() { } } - @Test public void requestCancelCrash() { List list = TestHelper.trackPluginErrors(); diff --git a/src/test/java/io/reactivex/subscribers/SafeSubscriberWithPluginTest.java b/src/test/java/io/reactivex/subscribers/SafeSubscriberWithPluginTest.java index 2f3df019ce..b95f00b3fd 100644 --- a/src/test/java/io/reactivex/subscribers/SafeSubscriberWithPluginTest.java +++ b/src/test/java/io/reactivex/subscribers/SafeSubscriberWithPluginTest.java @@ -171,6 +171,7 @@ public void onError(Throwable e) { safe.onError(new TestException()); } + @Test(expected = RuntimeException.class) @Ignore("Subscribers can't throw") public void testPluginExceptionWhileOnErrorThrowsAndUnsubscribeThrows() { @@ -195,6 +196,7 @@ public void onError(Throwable e) { safe.onError(new TestException()); } + @Test(expected = RuntimeException.class) @Ignore("Subscribers can't throw") public void testPluginExceptionWhenUnsubscribing2() { diff --git a/src/test/java/io/reactivex/subscribers/SerializedSubscriberTest.java b/src/test/java/io/reactivex/subscribers/SerializedSubscriberTest.java index a6d4d4411f..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; @@ -158,6 +157,7 @@ public void testMultiThreadedWithNPEinMiddle() { @Test public void runOutOfOrderConcurrencyTest() { ExecutorService tp = Executors.newFixedThreadPool(20); + List errors = TestHelper.trackPluginErrors(); try { TestConcurrencySubscriber tw = new TestConcurrencySubscriber(); // we need Synchronized + SafeSubscriber to handle synchronization plus life-cycle @@ -192,6 +192,10 @@ public void runOutOfOrderConcurrencyTest() { @SuppressWarnings("unused") int numNextEvents = tw.assertEvents(null); // no check of type since we don't want to test barging results here, just interleaving behavior // System.out.println("Number of events executed: " + numNextEvents); + + for (int i = 0; i < errors.size(); i++) { + TestHelper.assertUndeliverable(errors, i, RuntimeException.class); + } } catch (Throwable e) { fail("Concurrency test failed: " + e.getMessage()); e.printStackTrace(); @@ -202,6 +206,8 @@ public void runOutOfOrderConcurrencyTest() { } catch (InterruptedException e) { e.printStackTrace(); } + + RxJavaPlugins.reset(); } } @@ -957,25 +963,33 @@ public void onNext(Integer t) { @Test public void testErrorReentry() { - final AtomicReference> serial = new AtomicReference>(); + List errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference> serial = new AtomicReference>(); - TestSubscriber ts = new TestSubscriber() { - @Override - public void onNext(Integer v) { - serial.get().onError(new TestException()); - serial.get().onError(new TestException()); - super.onNext(v); - } - }; - SerializedSubscriber sobs = new SerializedSubscriber(ts); - sobs.onSubscribe(new BooleanSubscription()); - serial.set(sobs); + TestSubscriber ts = new TestSubscriber() { + @Override + public void onNext(Integer v) { + serial.get().onError(new TestException()); + serial.get().onError(new TestException()); + super.onNext(v); + } + }; + SerializedSubscriber sobs = new SerializedSubscriber(ts); + sobs.onSubscribe(new BooleanSubscription()); + serial.set(sobs); - sobs.onNext(1); + sobs.onNext(1); - ts.assertValue(1); - ts.assertError(TestException.class); + ts.assertValue(1); + ts.assertError(TestException.class); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } + @Test public void testCompleteReentry() { final AtomicReference> serial = new AtomicReference>(); @@ -1179,38 +1193,45 @@ public void startOnce() { @Test public void onCompleteOnErrorRace() { for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { - TestSubscriber ts = new TestSubscriber(); + List errors = TestHelper.trackPluginErrors(); + try { + TestSubscriber ts = new TestSubscriber(); - final SerializedSubscriber so = new SerializedSubscriber(ts); + final SerializedSubscriber so = new SerializedSubscriber(ts); - BooleanSubscription bs = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - so.onSubscribe(bs); + so.onSubscribe(bs); - final Throwable ex = new TestException(); + final Throwable ex = new TestException(); - Runnable r1 = new Runnable() { - @Override - public void run() { - so.onError(ex); - } - }; + Runnable r1 = new Runnable() { + @Override + public void run() { + so.onError(ex); + } + }; - Runnable r2 = new Runnable() { - @Override - public void run() { - so.onComplete(); - } - }; + Runnable r2 = new Runnable() { + @Override + public void run() { + so.onComplete(); + } + }; - TestHelper.race(r1, r2); + TestHelper.race(r1, r2); - ts.awaitDone(5, TimeUnit.SECONDS); + ts.awaitDone(5, TimeUnit.SECONDS); - if (ts.completions() != 0) { - ts.assertResult(); - } else { - ts.assertFailure(TestException.class).assertError(ex); + if (ts.completions() != 0) { + ts.assertResult(); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } else { + ts.assertFailure(TestException.class).assertError(ex); + assertTrue("" + errors, errors.isEmpty()); + } + } finally { + RxJavaPlugins.reset(); } } diff --git a/src/test/java/io/reactivex/subscribers/TestSubscriberTest.java b/src/test/java/io/reactivex/subscribers/TestSubscriberTest.java index 85cd58baad..6c634d8d91 100644 --- a/src/test/java/io/reactivex/subscribers/TestSubscriberTest.java +++ b/src/test/java/io/reactivex/subscribers/TestSubscriberTest.java @@ -79,7 +79,6 @@ public void testAssertNotMatchValue() { // FIXME different message pattern // thrown.expectMessage("Value at index: 1 expected to be [3] (Integer) but was: [2] (Integer)"); - ts.assertValues(1, 3); ts.assertValueCount(2); ts.assertTerminated(); @@ -746,7 +745,6 @@ public void onError(Throwable e) { ts.awaitTerminalEvent(); } - @Test public void createDelegate() { TestSubscriber ts1 = TestSubscriber.create(); @@ -929,8 +927,6 @@ public boolean test(Throwable t) { ts.assertValueCount(0); ts.assertNoValues(); - - } @Test @@ -1341,7 +1337,6 @@ public void assertTerminated2() { // expected } - ts = TestSubscriber.create(); ts.onSubscribe(new BooleanSubscription()); @@ -1611,7 +1606,6 @@ public void onComplete() { } } - @Test public void syncQueueThrows() { TestSubscriber ts = new TestSubscriber(); @@ -1826,7 +1820,6 @@ public void timeoutIndicated2() throws InterruptedException { } } - @Test public void timeoutIndicated3() throws InterruptedException { TestSubscriber ts = Flowable.never() @@ -2189,4 +2182,38 @@ public void awaitCount0() { TestSubscriber ts = TestSubscriber.create(); ts.awaitCount(0, TestWaitStrategy.SLEEP_1MS, 0); } + + @Test + public void assertValueSetWiderSet() { + Set set = new HashSet(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7)); + + Flowable.just(4, 5, 1, 3, 2) + .test() + .assertValueSet(set); + } + + @Test + public void assertValueSetExact() { + Set set = new HashSet(Arrays.asList(1, 2, 3, 4, 5)); + + Flowable.just(4, 5, 1, 3, 2) + .test() + .assertValueSet(set) + .assertValueCount(set.size()); + } + + @Test + public void assertValueSetMissing() { + Set set = new HashSet(Arrays.asList(0, 1, 2, 4, 5, 6, 7)); + + try { + Flowable.range(1, 5) + .test() + .assertValueSet(set); + + throw new RuntimeException("Should have failed"); + } catch (AssertionError ex) { + assertTrue(ex.getMessage(), ex.getMessage().contains("Value not in the expected collection: " + 3)); + } + } } diff --git a/src/test/java/io/reactivex/tck/BaseTck.java b/src/test/java/io/reactivex/tck/BaseTck.java index 42df479448..a06522a399 100644 --- a/src/test/java/io/reactivex/tck/BaseTck.java +++ b/src/test/java/io/reactivex/tck/BaseTck.java @@ -44,7 +44,6 @@ public Publisher createFailedPublisher() { return Flowable.error(new TestException()); } - @Override public long maxElementsFromPublisher() { return 1024; diff --git a/src/test/java/io/reactivex/validators/CheckLocalVariablesInTests.java b/src/test/java/io/reactivex/validators/CheckLocalVariablesInTests.java index 9877a66cc1..8f8acaf3d4 100644 --- a/src/test/java/io/reactivex/validators/CheckLocalVariablesInTests.java +++ b/src/test/java/io/reactivex/validators/CheckLocalVariablesInTests.java @@ -332,6 +332,11 @@ public void atomicSubscriptionAsS() throws Exception { findPattern("AtomicReference\\s+s[0-9]?;", true); } + @Test + public void atomicSubscriptionAsSInit() throws Exception { + findPattern("AtomicReference\\s+s[0-9]?\\s", true); + } + @Test public void atomicSubscriptionAsSubscription() throws Exception { findPattern("AtomicReference\\s+subscription[0-9]?", true); diff --git a/src/test/java/io/reactivex/validators/JavadocForAnnotations.java b/src/test/java/io/reactivex/validators/JavadocForAnnotations.java index 9dae922016..3912ced5ce 100644 --- a/src/test/java/io/reactivex/validators/JavadocForAnnotations.java +++ b/src/test/java/io/reactivex/validators/JavadocForAnnotations.java @@ -104,7 +104,6 @@ static final void scanFor(StringBuilder sourceCode, String annotation, String in } } - static final void scanForBadMethod(StringBuilder sourceCode, String annotation, String inDoc, StringBuilder e, String baseClassName) { int index = 0; diff --git a/src/test/java/io/reactivex/validators/NewLinesBeforeAnnotation.java b/src/test/java/io/reactivex/validators/NewLinesBeforeAnnotation.java new file mode 100644 index 0000000000..e7c1c13bb3 --- /dev/null +++ b/src/test/java/io/reactivex/validators/NewLinesBeforeAnnotation.java @@ -0,0 +1,162 @@ +/** + * 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.validators; + +import java.io.*; +import java.util.*; + +import org.junit.Test; + +/** + * These tests verify the code style that a typical closing curly brace + * and the next annotation @ indicator + * are not separated by less than or more than one empty line. + *

      Thus this is detected: + *

      
      + * }
      + * @Override
      + * 
      + *

      + * as well as + *

      
      + * }
      + * 
      + * 
      + * @Override
      + * 
      + */ +public class NewLinesBeforeAnnotation { + + @Test + public void missingEmptyNewLine() throws Exception { + findPattern(0); + } + + @Test + public void tooManyEmptyNewLines2() throws Exception { + findPattern(2); + } + + @Test + public void tooManyEmptyNewLines3() throws Exception { + findPattern(3); + } + + @Test + public void tooManyEmptyNewLines4() throws Exception { + findPattern(4); + } + + @Test + public void tooManyEmptyNewLines5() throws Exception { + findPattern(5); + } + + static void findPattern(int newLines) throws Exception { + File f = MaybeNo2Dot0Since.findSource("Flowable"); + if (f == null) { + System.out.println("Unable to find sources of RxJava"); + return; + } + + Queue dirs = new ArrayDeque(); + + StringBuilder fail = new StringBuilder(); + fail.append("The following code pattern was found: "); + fail.append("\\}\\R"); + for (int i = 0; i < newLines; i++) { + fail.append("\\R"); + } + fail.append("[ ]+@\n"); + + File parent = f.getParentFile(); + + dirs.offer(new File(parent.getAbsolutePath().replace('\\', '/'))); + dirs.offer(new File(parent.getAbsolutePath().replace('\\', '/').replace("src/main/java", "src/test/java"))); + + int total = 0; + + while (!dirs.isEmpty()) { + f = dirs.poll(); + + File[] list = f.listFiles(); + if (list != null && list.length != 0) { + + for (File u : list) { + if (u.isDirectory()) { + dirs.offer(u); + } else { + String fname = u.getName(); + if (fname.endsWith(".java")) { + + List lines = new ArrayList(); + BufferedReader in = new BufferedReader(new FileReader(u)); + try { + for (;;) { + String line = in.readLine(); + if (line == null) { + break; + } + lines.add(line); + } + } finally { + in.close(); + } + + for (int i = 0; i < lines.size() - 1; i++) { + String line = lines.get(i); + if (line.endsWith("}") && !line.trim().startsWith("*") && !line.trim().startsWith("//")) { + int emptyLines = 0; + boolean found = false; + for (int j = i + 1; j < lines.size(); j++) { + String line2 = lines.get(j); + if (line2.trim().startsWith("@")) { + found = true; + break; + } + if (!line2.trim().isEmpty()) { + break; + } + emptyLines++; + } + + if (emptyLines == newLines && found) { + fail + .append(fname) + .append("#L").append(i + 1) + .append(" "); + for (int k = 0; k < emptyLines + 2; k++) { + fail + .append(lines.get(k + i)) + .append("\\R"); + } + fail.append("\n"); + total++; + } + } + } + } + } + } + } + } + if (total != 0) { + fail.append("Found ") + .append(total) + .append(" instances"); + System.out.println(fail); + throw new AssertionError(fail.toString()); + } + } +} diff --git a/src/test/java/io/reactivex/validators/ParamValidationCheckerTest.java b/src/test/java/io/reactivex/validators/ParamValidationCheckerTest.java index 136ba29581..2d7a9649b8 100644 --- a/src/test/java/io/reactivex/validators/ParamValidationCheckerTest.java +++ b/src/test/java/io/reactivex/validators/ParamValidationCheckerTest.java @@ -261,6 +261,10 @@ public void checkParallelFlowable() { addOverride(new ParamOverride(Completable.class, 0, ParamMode.ANY, "delay", Long.TYPE, TimeUnit.class, Scheduler.class)); addOverride(new ParamOverride(Completable.class, 0, ParamMode.ANY, "delay", Long.TYPE, TimeUnit.class, Scheduler.class, Boolean.TYPE)); + // negative time is considered as zero time + addOverride(new ParamOverride(Completable.class, 0, ParamMode.ANY, "delaySubscription", Long.TYPE, TimeUnit.class)); + addOverride(new ParamOverride(Completable.class, 0, ParamMode.ANY, "delaySubscription", Long.TYPE, TimeUnit.class, Scheduler.class)); + // zero repeat is allowed addOverride(new ParamOverride(Completable.class, 0, ParamMode.NON_NEGATIVE, "repeat", Long.TYPE)); @@ -326,7 +330,6 @@ public void checkParallelFlowable() { addOverride(new ParamOverride(Single.class, 0, ParamMode.ANY, "delay", Long.TYPE, TimeUnit.class, Scheduler.class)); addOverride(new ParamOverride(Single.class, 0, ParamMode.ANY, "delay", Long.TYPE, TimeUnit.class, Scheduler.class, Boolean.TYPE)); - // zero repeat is allowed addOverride(new ParamOverride(Single.class, 0, ParamMode.NON_NEGATIVE, "repeat", Long.TYPE)); diff --git a/src/test/java/io/reactivex/validators/TooManyEmptyNewLines.java b/src/test/java/io/reactivex/validators/TooManyEmptyNewLines.java new file mode 100644 index 0000000000..2da65b36c8 --- /dev/null +++ b/src/test/java/io/reactivex/validators/TooManyEmptyNewLines.java @@ -0,0 +1,132 @@ +/** + * 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.validators; + +import java.io.*; +import java.util.*; + +import org.junit.Test; + +/** + * Test verifying there are no 2..5 empty newlines in the code. + */ +public class TooManyEmptyNewLines { + + @Test + public void tooManyEmptyNewLines2() throws Exception { + findPattern(2); + } + + @Test + public void tooManyEmptyNewLines3() throws Exception { + findPattern(3); + } + + @Test + public void tooManyEmptyNewLines4() throws Exception { + findPattern(4); + } + + @Test + public void tooManyEmptyNewLines5() throws Exception { + findPattern(5); + } + + static void findPattern(int newLines) throws Exception { + File f = MaybeNo2Dot0Since.findSource("Flowable"); + if (f == null) { + System.out.println("Unable to find sources of TestHelper.findSourceDir()"); + return; + } + + Queue dirs = new ArrayDeque(); + + StringBuilder fail = new StringBuilder(); + fail.append("The following code pattern was found: "); + fail.append("\\R"); + for (int i = 0; i < newLines; i++) { + fail.append("\\R"); + } + fail.append("\n"); + + File parent = f.getParentFile(); + + dirs.offer(new File(parent.getAbsolutePath().replace('\\', '/'))); + dirs.offer(new File(parent.getAbsolutePath().replace('\\', '/').replace("src/main/java", "src/test/java"))); + + int total = 0; + + while (!dirs.isEmpty()) { + f = dirs.poll(); + + File[] list = f.listFiles(); + if (list != null && list.length != 0) { + + for (File u : list) { + if (u.isDirectory()) { + dirs.offer(u); + } else { + String fname = u.getName(); + if (fname.endsWith(".java")) { + + List lines = new ArrayList(); + BufferedReader in = new BufferedReader(new FileReader(u)); + try { + for (;;) { + String line = in.readLine(); + if (line == null) { + break; + } + lines.add(line); + } + } finally { + in.close(); + } + + for (int i = 0; i < lines.size() - newLines; i++) { + String line1 = lines.get(i); + if (line1.isEmpty()) { + int c = 1; + for (int j = i + 1; j < lines.size(); j++) { + if (lines.get(j).isEmpty()) { + c++; + } else { + break; + } + } + + if (c == newLines) { + fail + .append(fname) + .append("#L").append(i + 1) + .append("\n"); + total++; + i += c; + } + } + } + } + } + } + } + } + if (total != 0) { + fail.append("Found ") + .append(total) + .append(" instances"); + System.out.println(fail); + throw new AssertionError(fail.toString()); + } + } +}