From 26500c5ca59a10b07880c63a78881f65bdd60247 Mon Sep 17 00:00:00 2001 From: Sebas LG Date: Mon, 28 Mar 2016 15:26:33 +0200 Subject: [PATCH 001/294] 1.x: Fix typos in documentation and some code --- build.gradle | 2 +- src/main/java/rx/Completable.java | 18 ++++++------ src/main/java/rx/Observable.java | 28 +++++++++---------- src/main/java/rx/Producer.java | 2 +- src/main/java/rx/Single.java | 6 ++-- src/main/java/rx/Subscriber.java | 4 +-- .../internal/operators/BackpressureUtils.java | 4 +-- .../operators/OnSubscribeGroupJoin.java | 2 +- .../operators/OperatorBufferWithTime.java | 2 +- .../rx/internal/operators/OperatorMerge.java | 2 +- .../OperatorOnBackpressureLatest.java | 2 +- .../internal/operators/OperatorPublish.java | 2 +- .../rx/internal/operators/OperatorReplay.java | 6 ++-- .../internal/operators/OperatorTakeTimed.java | 2 +- .../rx/internal/producers/QueuedProducer.java | 2 +- .../producers/QueuedValueProducer.java | 2 +- .../GenericScheduledExecutorService.java | 2 +- .../util/BackpressureDrainManager.java | 2 +- .../unsafe/ConcurrentCircularArrayQueue.java | 2 +- .../rx/observables/BlockingObservable.java | 2 +- .../rx/observables/GroupedObservable.java | 2 +- .../RxJavaObservableExecutionHook.java | 24 ++++++++-------- src/main/java/rx/plugins/RxJavaPlugins.java | 2 +- .../java/rx/plugins/RxJavaSchedulersHook.java | 2 +- .../rx/plugins/RxJavaSingleExecutionHook.java | 24 ++++++++-------- src/main/java/rx/schedulers/Schedulers.java | 2 +- src/main/java/rx/subjects/ReplaySubject.java | 4 +-- src/main/java/rx/subjects/TestSubject.java | 6 ++-- src/test/java/rx/BackpressureTests.java | 6 ++-- src/test/java/rx/SingleTest.java | 2 +- src/test/java/rx/SubscriberTest.java | 2 +- .../java/rx/exceptions/OnNextValueTest.java | 2 +- .../java/rx/exceptions/TestException.java | 2 +- .../operators/OnSubscribeUsingTest.java | 2 +- .../operators/OperatorConcatTest.java | 2 +- .../operators/OperatorFlatMapTest.java | 2 +- .../OperatorOnBackpressureBufferTest.java | 2 +- .../OperatorPublishFunctionTest.java | 4 +-- .../internal/operators/OperatorRetryTest.java | 2 +- .../operators/OperatorTimeoutTests.java | 2 +- .../OperatorTimeoutWithSelectorTest.java | 2 +- .../internal/operators/OperatorZipTest.java | 2 +- .../operators/SingleOnSubscribeUsingTest.java | 2 +- .../schedulers/TrampolineSchedulerTest.java | 2 +- .../ReplaySubjectBoundedConcurrencyTest.java | 2 +- .../ReplaySubjectConcurrencyTest.java | 2 +- .../CompositeSubscriptionTest.java | 2 +- .../SerialSubscriptionTests.java | 2 +- .../rx/test/TestObstructionDetection.java | 2 +- 49 files changed, 104 insertions(+), 104 deletions(-) diff --git a/build.gradle b/build.gradle index f465376da3..1806a43120 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ javadoc { options { windowTitle = "RxJava Javadoc ${project.version}" } - // Clear the following options to make the docs consitent with the old format + // Clear the following options to make the docs consistent with the old format options.addStringOption('top').value = '' options.addStringOption('doctitle').value = '' options.addStringOption('header').value = '' diff --git a/src/main/java/rx/Completable.java b/src/main/java/rx/Completable.java index f2e752c2b2..eb3cf76d54 100644 --- a/src/main/java/rx/Completable.java +++ b/src/main/java/rx/Completable.java @@ -752,7 +752,7 @@ static T requireNonNull(T o) { } /** - * Returns a Completable instance that fires its onComplete event after the given delay ellapsed. + * Returns a Completable instance that fires its onComplete event after the given delay elapsed. * @param delay the delay time * @param unit the delay unit * @return the new Completable instance @@ -762,7 +762,7 @@ public static Completable timer(long delay, TimeUnit unit) { } /** - * Returns a Completable instance that fires its onComplete event after the given delay ellapsed + * Returns a Completable instance that fires its onComplete event after the given delay elapsed * by using the supplied scheduler. * @param delay the delay time * @param unit the delay unit @@ -1040,7 +1040,7 @@ public void onSubscribe(Subscription d) { * @param timeout the timeout value * @param unit the timeout unit * @return true if the this Completable instance completed normally within the time limit, - * false if the timeout ellapsed before this Completable terminated. + * false if the timeout elapsed before this Completable terminated. * @throws RuntimeException wrapping an InterruptedException if the current thread is interrupted */ public final boolean await(long timeout, TimeUnit unit) { @@ -1239,7 +1239,7 @@ public final Completable doOnCompleted(Action0 onCompleted) { } /** - * Returns a Completable which calls the giveon onUnsubscribe callback if the child subscriber cancels + * Returns a Completable which calls the given onUnsubscribe callback if the child subscriber cancels * the subscription. * @param onUnsubscribe the callback to call when the child subscriber cancels the subscription * @return the new Completable instance @@ -1395,7 +1395,7 @@ public final Observable endWith(Observable next) { } /** - * Returns a Completable instace that calls the given onAfterComplete callback after this + * Returns a Completable instance that calls the given onAfterComplete callback after this * Completable completes normally. * @param onAfterComplete the callback to call after this Completable emits an onComplete event. * @return the new Completable instance @@ -1448,10 +1448,10 @@ public void onSubscribe(Subscription d) { /** * Subscribes to this Completable instance and blocks until it terminates or the specified timeout - * ellapses, then returns null for normal termination or the emitted exception if any. + * elapses, then returns null for normal termination or the emitted exception if any. * @return the throwable if this terminated with an error, null otherwise * @throws RuntimeException that wraps an InterruptedException if the wait is interrupted or - * TimeoutException if the specified timeout ellapsed before it + * TimeoutException if the specified timeout elapsed before it */ public final Throwable get(long timeout, TimeUnit unit) { requireNonNull(unit); @@ -2128,7 +2128,7 @@ public void call(Subscriber s) { } /** - * Convers this Completable into a Single which when this Completable completes normally, + * Converts this Completable into a Single which when this Completable completes normally, * calls the given supplier and emits its returned value through onSuccess. * @param completionValueFunc0 the value supplier called when this Completable completes normally * @return the new Single instance @@ -2175,7 +2175,7 @@ public void onSubscribe(Subscription d) { } /** - * Convers this Completable into a Single which when this Completable completes normally, + * Converts this Completable into a Single which when this Completable completes normally, * emits the given value through onSuccess. * @param completionValue the value to emit when this Completable completes normally * @return the new Single instance diff --git a/src/main/java/rx/Observable.java b/src/main/java/rx/Observable.java index b89bc431e2..74fcf88fe9 100644 --- a/src/main/java/rx/Observable.java +++ b/src/main/java/rx/Observable.java @@ -102,7 +102,7 @@ public static Observable create(OnSubscribe f) { * *

Note: the {@code SyncOnSubscribe} provides a generic way to fulfill data by iterating * over a (potentially stateful) function (e.g. reading data off of a channel, a parser, ). If your - * data comes directly from an asyrchronous/potentially concurrent source then consider using the + * data comes directly from an asynchronous/potentially concurrent source then consider using the * {@link Observable#create(AsyncOnSubscribe) asynchronous overload}. * *

@@ -3052,7 +3052,7 @@ public static Observable using( * Constructs an Observable that creates a dependent resource object which is disposed of just before * termination if you have set {@code disposeEagerly} to {@code true} and unsubscription does not occur * before termination. Otherwise resource disposal will occur on unsubscription. Eager disposal is - * particularly appropriate for a synchronous Observable that resuses resources. {@code disposeAction} will + * particularly appropriate for a synchronous Observable that reuses resources. {@code disposeAction} will * only be called once per subscription. *

* @@ -3094,7 +3094,7 @@ public static Observable using( * item emitted by each of those Observables; and so forth. *

* The resulting {@code Observable} returned from {@code zip} will invoke {@code onNext} as many times as - * the number of {@code onNext} invokations of the source Observable that emits the fewest items. + * the number of {@code onNext} invocations of the source Observable that emits the fewest items. *

* *

@@ -3128,7 +3128,7 @@ public static Observable zip(Iterable> ws, FuncN< * function applied to the second item emitted by each of those Observables; and so forth. *

* The resulting {@code Observable} returned from {@code zip} will invoke {@code onNext} as many times as - * the number of {@code onNext} invokations of the source Observable that emits the fewest items. + * the number of {@code onNext} invocations of the source Observable that emits the fewest items. *

* *

@@ -3900,7 +3900,7 @@ public final Observable> buffer(Observable boundary, int initialC * subscribe/unsubscribe behavior of all the {@link Subscriber}s. *

* When you call {@code cache}, it does not yet subscribe to the source Observable and so does not yet - * begin cacheing items. This only happens when the first Subscriber calls the resulting Observable's + * begin caching items. This only happens when the first Subscriber calls the resulting Observable's * {@code subscribe} method. *

* Note: You sacrifice the ability to unsubscribe from the origin when you use the {@code cache} @@ -3943,7 +3943,7 @@ public final Observable cache(int initialCapacity) { * subscribe/unsubscribe behavior of all the {@link Subscriber}s. *

* When you call {@code cache}, it does not yet subscribe to the source Observable and so does not yet - * begin cacheing items. This only happens when the first Subscriber calls the resulting Observable's + * begin caching items. This only happens when the first Subscriber calls the resulting Observable's * {@code subscribe} method. *

* Note: You sacrifice the ability to unsubscribe from the origin when you use the {@code cache} @@ -4367,7 +4367,7 @@ public final Observable switchIfEmpty(Observable alternate) { } /** - * Returns an Observable that delays the subscription to and emissions from the souce Observable via another + * Returns an Observable that delays the subscription to and emissions from the source Observable via another * Observable on a per-item basis. *

* @@ -5357,7 +5357,7 @@ public final Observable concatMapEager(Func1 * *

@@ -5821,7 +5821,7 @@ public final Observable flatMapIterable(Func1 * the collection element type * @param - * the type of item emited by the resulting Observable + * the type of item emitted by the resulting Observable * @param collectionSelector * a function that returns an Iterable sequence of values for each item emitted by the source * Observable @@ -5851,7 +5851,7 @@ public final Observable flatMapIterable(Func1 * the collection element type * @param - * the type of item emited by the resulting Observable + * the type of item emitted by the resulting Observable * @param collectionSelector * a function that returns an Iterable sequence of values for each item emitted by the source * Observable @@ -6813,7 +6813,7 @@ public final Observable reduce(Func2 accumulator) { *

* *

- * This technique, which is called "reduce" here, is sometimec called "aggregate," "fold," "accumulate," + * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method * that does a similar operation on lists. *

@@ -10355,7 +10355,7 @@ public final Observable> window(long timespan, long timeshift, Tim * new window * @param unit * the unit of time that applies to the {@code timespan} argument - * @return an Observable that emits connected, non-overlapping windows represending items emitted by the + * @return an Observable that emits connected, non-overlapping windows representing items emitted by the * source Observable during fixed, consecutive durations * @see ReactiveX operators documentation: Window */ @@ -10581,9 +10581,9 @@ private static class Holder { } /** - * Returns a singleton instance of NeverObservble (cast to the generic type). + * Returns a singleton instance of NeverObservable (cast to the generic type). * - * @return + * @return singleton instance of NeverObservable (cast to the generic type) */ @SuppressWarnings("unchecked") static NeverObservable instance() { diff --git a/src/main/java/rx/Producer.java b/src/main/java/rx/Producer.java index 9bb9cc22d1..fb7f211e7c 100644 --- a/src/main/java/rx/Producer.java +++ b/src/main/java/rx/Producer.java @@ -21,7 +21,7 @@ * backpressure). * *

The request amount only affects calls to {@link Subscriber#onNext(Object)}; onError and onCompleted may appear without - * requrests. + * requests. * *

However, backpressure is somewhat optional in RxJava 1.x and Subscribers may not * receive a Producer via their {@link Subscriber#setProducer(Producer)} method and will run diff --git a/src/main/java/rx/Single.java b/src/main/java/rx/Single.java index f5e1156acc..628e716ed8 100644 --- a/src/main/java/rx/Single.java +++ b/src/main/java/rx/Single.java @@ -1719,7 +1719,7 @@ public final Subscription subscribe(Subscriber subscriber) { subscriber = new SafeSubscriber(subscriber); } - // The code below is exactly the same an unsafeSubscribe but not used because it would add a sigificent depth to alreay huge call stacks. + // The code below is exactly the same an unsafeSubscribe but not used because it would add a significant depth to already huge call stacks. try { // allow the hook to intercept and/or decorate hook.onSubscribeStart(this, onSubscribe).call(subscriber); @@ -2186,7 +2186,7 @@ public final Single timeout(long timeout, TimeUnit timeUnit, SingleReactiveX operators documentation: Timeout */ public final Single timeout(long timeout, TimeUnit timeUnit, Single other, Scheduler scheduler) { @@ -2630,7 +2630,7 @@ public static Single using( * Constructs an Single that creates a dependent resource object which is disposed of just before * termination if you have set {@code disposeEagerly} to {@code true} and unsubscription does not occur * before termination. Otherwise resource disposal will occur on unsubscription. Eager disposal is - * particularly appropriate for a synchronous Single that resuses resources. {@code disposeAction} will + * particularly appropriate for a synchronous Single that reuses resources. {@code disposeAction} will * only be called once per subscription. *

* diff --git a/src/main/java/rx/Subscriber.java b/src/main/java/rx/Subscriber.java index 67ac611e4c..cfbfcc2496 100644 --- a/src/main/java/rx/Subscriber.java +++ b/src/main/java/rx/Subscriber.java @@ -193,9 +193,9 @@ public void setProducer(Producer p) { toRequest = requested; producer = p; if (subscriber != null) { - // middle operator ... we pass thru unless a request has been made + // middle operator ... we pass through unless a request has been made if (toRequest == NOT_SET) { - // we pass-thru to the next producer as nothing has been requested + // we pass through to the next producer as nothing has been requested passToSubscriber = true; } } diff --git a/src/main/java/rx/internal/operators/BackpressureUtils.java b/src/main/java/rx/internal/operators/BackpressureUtils.java index cfbe282901..3d199567c6 100644 --- a/src/main/java/rx/internal/operators/BackpressureUtils.java +++ b/src/main/java/rx/internal/operators/BackpressureUtils.java @@ -246,10 +246,10 @@ static void postCompleteDrain(AtomicLong requested, Queue queue, Subscrib * in which we know the queue won't change anymore (i.e., done is always true * when looking at the classical algorithm and there is no error). * - * Note that we don't check for cancellation or emptyness upfront for two reasons: + * Note that we don't check for cancellation or emptiness upfront for two reasons: * 1) if e != r, the loop will do this and we quit appropriately * 2) if e == r, then either there was no outstanding requests or we emitted the requested amount - * and the execution simply falls to the e == r check below which checks for emptyness anyway. + * and the execution simply falls to the e == r check below which checks for emptiness anyway. */ while (e != r) { diff --git a/src/main/java/rx/internal/operators/OnSubscribeGroupJoin.java b/src/main/java/rx/internal/operators/OnSubscribeGroupJoin.java index 4b7509c2d9..a8f13fb5e7 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeGroupJoin.java +++ b/src/main/java/rx/internal/operators/OnSubscribeGroupJoin.java @@ -28,7 +28,7 @@ import rx.subscriptions.*; /** - * Corrrelates two sequences when they overlap and groups the results. + * Correlates two sequences when they overlap and groups the results. * * @see MSDN: Observable.GroupJoin * @param the left value type diff --git a/src/main/java/rx/internal/operators/OperatorBufferWithTime.java b/src/main/java/rx/internal/operators/OperatorBufferWithTime.java index bbb723d2b3..b13fe2fa9e 100644 --- a/src/main/java/rx/internal/operators/OperatorBufferWithTime.java +++ b/src/main/java/rx/internal/operators/OperatorBufferWithTime.java @@ -90,7 +90,7 @@ public Subscriber call(final Subscriber> child) { bsub.scheduleChunk(); return bsub; } - /** Subscriber when the buffer chunking time and lenght differ. */ + /** Subscriber when the buffer chunking time and length differ. */ final class InexactSubscriber extends Subscriber { final Subscriber> child; final Worker inner; diff --git a/src/main/java/rx/internal/operators/OperatorMerge.java b/src/main/java/rx/internal/operators/OperatorMerge.java index a9c7b86b09..69e92bb08e 100644 --- a/src/main/java/rx/internal/operators/OperatorMerge.java +++ b/src/main/java/rx/internal/operators/OperatorMerge.java @@ -614,7 +614,7 @@ void emitLoop() { } /* - * We need to read done before innerSubscribers because innerSubcribers are added + * We need to read done before innerSubscribers because innerSubscribers are added * before done is set to true. If it were the other way around, we could read an empty * innerSubscribers, get paused and then read a done flag but an async producer * might have added more subscribers between the two. diff --git a/src/main/java/rx/internal/operators/OperatorOnBackpressureLatest.java b/src/main/java/rx/internal/operators/OperatorOnBackpressureLatest.java index 2bf909289e..15078b0614 100644 --- a/src/main/java/rx/internal/operators/OperatorOnBackpressureLatest.java +++ b/src/main/java/rx/internal/operators/OperatorOnBackpressureLatest.java @@ -50,7 +50,7 @@ public Subscriber call(Subscriber child) { return parent; } /** - * A terminatable producer which emits the latest items on request. + * A terminable producer which emits the latest items on request. * @param */ static final class LatestEmitter extends AtomicLong implements Producer, Subscription, Observer { diff --git a/src/main/java/rx/internal/operators/OperatorPublish.java b/src/main/java/rx/internal/operators/OperatorPublish.java index bd66fc86ab..347905e06e 100644 --- a/src/main/java/rx/internal/operators/OperatorPublish.java +++ b/src/main/java/rx/internal/operators/OperatorPublish.java @@ -486,7 +486,7 @@ void dispatch() { try { for (;;) { /* - * We need to read terminalEvent before checking the queue for emptyness because + * We need to read terminalEvent before checking the queue for emptiness because * all enqueue happens before setting the terminal event. * If it were the other way around, when the emission is paused between * checking isEmpty and checking terminalEvent, some other thread might diff --git a/src/main/java/rx/internal/operators/OperatorReplay.java b/src/main/java/rx/internal/operators/OperatorReplay.java index a76f2f3c0b..f4af56bcb9 100644 --- a/src/main/java/rx/internal/operators/OperatorReplay.java +++ b/src/main/java/rx/internal/operators/OperatorReplay.java @@ -377,7 +377,7 @@ boolean add(InnerProducer producer) { if (producers.compareAndSet(c, u)) { return true; } - // if failed, some other operation succeded (another add, remove or termination) + // if failed, some other operation succeeded (another add, remove or termination) // so retry } } @@ -633,7 +633,7 @@ public void request(long n) { if (compareAndSet(r, u)) { // increment the total request counter addTotalRequested(n); - // if successful, notify the parent dispacher this child can receive more + // if successful, notify the parent dispatcher this child can receive more // elements parent.manageRequests(); @@ -687,7 +687,7 @@ public long produced(long n) { } // try updating the request value if (compareAndSet(r, u)) { - // and return the udpated value + // and return the updated value return u; } // otherwise, some concurrent activity happened and we need to retry diff --git a/src/main/java/rx/internal/operators/OperatorTakeTimed.java b/src/main/java/rx/internal/operators/OperatorTakeTimed.java index ea56be87ac..faa95b4b79 100644 --- a/src/main/java/rx/internal/operators/OperatorTakeTimed.java +++ b/src/main/java/rx/internal/operators/OperatorTakeTimed.java @@ -24,7 +24,7 @@ import rx.observers.SerializedSubscriber; /** - * Takes values from the source until the specific time ellapses. + * Takes values from the source until the specific time elapses. * * @param * the result value type diff --git a/src/main/java/rx/internal/producers/QueuedProducer.java b/src/main/java/rx/internal/producers/QueuedProducer.java index 51747dd9b9..ed892bab30 100644 --- a/src/main/java/rx/internal/producers/QueuedProducer.java +++ b/src/main/java/rx/internal/producers/QueuedProducer.java @@ -76,7 +76,7 @@ public void request(long n) { } /** - * Offers a value to this producer and tries to emit any queud values + * Offers a value to this producer and tries to emit any queued values * if the child requests allow it. * @param value the value to enqueue and attempt to drain * @return true if the queue accepted the offer, false otherwise diff --git a/src/main/java/rx/internal/producers/QueuedValueProducer.java b/src/main/java/rx/internal/producers/QueuedValueProducer.java index d165a412b7..53853f2ccc 100644 --- a/src/main/java/rx/internal/producers/QueuedValueProducer.java +++ b/src/main/java/rx/internal/producers/QueuedValueProducer.java @@ -73,7 +73,7 @@ public void request(long n) { } /** - * Offers a value to this producer and tries to emit any queud values + * Offers a value to this producer and tries to emit any queued values * if the child requests allow it. * @param value the value to enqueue and attempt to drain * @return true if the queue accepted the offer, false otherwise diff --git a/src/main/java/rx/internal/schedulers/GenericScheduledExecutorService.java b/src/main/java/rx/internal/schedulers/GenericScheduledExecutorService.java index 87f7ec5f88..e322945068 100644 --- a/src/main/java/rx/internal/schedulers/GenericScheduledExecutorService.java +++ b/src/main/java/rx/internal/schedulers/GenericScheduledExecutorService.java @@ -44,7 +44,7 @@ public final class GenericScheduledExecutorService implements SchedulerLifecycle SHUTDOWN.shutdown(); } - /* Schedulers needs acces to this in order to work with the lifecycle. */ + /* Schedulers needs access to this in order to work with the lifecycle. */ public final static GenericScheduledExecutorService INSTANCE = new GenericScheduledExecutorService(); private final AtomicReference executor; diff --git a/src/main/java/rx/internal/util/BackpressureDrainManager.java b/src/main/java/rx/internal/util/BackpressureDrainManager.java index c90e9591df..3e670e0e53 100644 --- a/src/main/java/rx/internal/util/BackpressureDrainManager.java +++ b/src/main/java/rx/internal/util/BackpressureDrainManager.java @@ -72,7 +72,7 @@ public interface BackpressureQueueCallback { /** The callbacks to manage the drain. */ protected final BackpressureQueueCallback actual; /** - * Constructs a backpressure drain manager with 0 requesedCount, + * Constructs a backpressure drain manager with 0 requestedCount, * no terminal event and not emitting. * @param actual he queue callback to check for new element availability */ diff --git a/src/main/java/rx/internal/util/unsafe/ConcurrentCircularArrayQueue.java b/src/main/java/rx/internal/util/unsafe/ConcurrentCircularArrayQueue.java index 86a8db0b19..a8b9990b56 100644 --- a/src/main/java/rx/internal/util/unsafe/ConcurrentCircularArrayQueue.java +++ b/src/main/java/rx/internal/util/unsafe/ConcurrentCircularArrayQueue.java @@ -29,7 +29,7 @@ abstract class ConcurrentCircularArrayQueueL0Pad extends AbstractQueue imp /** * A concurrent access enabling class used by circular array based queues this class exposes an offset computation * method along with differently memory fenced load/store methods into the underlying array. The class is pre-padded and - * the array is padded on either side to help with False sharing prvention. It is expected theat subclasses handle post + * the array is padded on either side to help with False sharing prevention. It is expected that subclasses handle post * padding. *

* Offset calculation is separate from access to enable the reuse of a give compute offset. diff --git a/src/main/java/rx/observables/BlockingObservable.java b/src/main/java/rx/observables/BlockingObservable.java index c5b3588e32..a44bf85558 100644 --- a/src/main/java/rx/observables/BlockingObservable.java +++ b/src/main/java/rx/observables/BlockingObservable.java @@ -543,7 +543,7 @@ public void onCompleted() { /** Constant indicating the setProducer method should be called. */ static final Object SET_PRODUCER = new Object(); - /** Indicates an unsubscripton happened */ + /** Indicates an unsubscription happened */ static final Object UNSUBSCRIBE = new Object(); /** diff --git a/src/main/java/rx/observables/GroupedObservable.java b/src/main/java/rx/observables/GroupedObservable.java index ad9ceb9370..0dccce036d 100644 --- a/src/main/java/rx/observables/GroupedObservable.java +++ b/src/main/java/rx/observables/GroupedObservable.java @@ -95,7 +95,7 @@ protected GroupedObservable(K key, OnSubscribe onSubscribe) { } /** - * Returns the key that identifies the group of items emited by this {@code GroupedObservable} + * Returns the key that identifies the group of items emitted by this {@code GroupedObservable} * * @return the key that the items emitted by this {@code GroupedObservable} were grouped by */ diff --git a/src/main/java/rx/plugins/RxJavaObservableExecutionHook.java b/src/main/java/rx/plugins/RxJavaObservableExecutionHook.java index 22bca81286..0413fe6a32 100644 --- a/src/main/java/rx/plugins/RxJavaObservableExecutionHook.java +++ b/src/main/java/rx/plugins/RxJavaObservableExecutionHook.java @@ -44,12 +44,12 @@ public abstract class RxJavaObservableExecutionHook { * Invoked during the construction by {@link Observable#create(OnSubscribe)} *

* This can be used to decorate or replace the onSubscribe function or just perform extra - * logging, metrics and other such things and pass-thru the function. + * logging, metrics and other such things and pass through the function. * * @param f * original {@link OnSubscribe}<{@code T}> to be executed * @return {@link OnSubscribe}<{@code T}> function that can be modified, decorated, replaced or just - * returned as a pass-thru + * returned as a pass through */ public OnSubscribe onCreate(OnSubscribe f) { return f; @@ -59,15 +59,15 @@ public OnSubscribe onCreate(OnSubscribe f) { * Invoked before {@link Observable#subscribe(rx.Subscriber)} is about to be executed. *

* This can be used to decorate or replace the onSubscribe function or just perform extra - * logging, metrics and other such things and pass-thru the function. + * logging, metrics and other such things and pass through the function. * * @param onSubscribe * original {@link OnSubscribe}<{@code T}> to be executed * @return {@link OnSubscribe}<{@code T}> function that can be modified, decorated, replaced or just - * returned as a pass-thru + * returned as a pass through */ public OnSubscribe onSubscribeStart(Observable observableInstance, final OnSubscribe onSubscribe) { - // pass-thru by default + // pass through by default return onSubscribe; } @@ -76,15 +76,15 @@ public OnSubscribe onSubscribeStart(Observable observableIns * {@link Subscription}. *

* This can be used to decorate or replace the {@link Subscription} instance or just perform extra logging, - * metrics and other such things and pass-thru the subscription. + * metrics and other such things and pass through the subscription. * * @param subscription * original {@link Subscription} * @return {@link Subscription} subscription that can be modified, decorated, replaced or just returned as a - * pass-thru + * pass through */ public Subscription onSubscribeReturn(Subscription subscription) { - // pass-thru by default + // pass through by default return subscription; } @@ -96,10 +96,10 @@ public Subscription onSubscribeReturn(Subscription subscription) { * * @param e * Throwable thrown by {@link Observable#subscribe(Subscriber)} - * @return Throwable that can be decorated, replaced or just returned as a pass-thru + * @return Throwable that can be decorated, replaced or just returned as a pass through */ public Throwable onSubscribeError(Throwable e) { - // pass-thru by default + // pass through by default return e; } @@ -108,12 +108,12 @@ public Throwable onSubscribeError(Throwable e) { * {@link Observable} and the return value is used as the lifted function *

* This can be used to decorate or replace the {@link Operator} instance or just perform extra - * logging, metrics and other such things and pass-thru the onSubscribe. + * logging, metrics and other such things and pass through the onSubscribe. * * @param lift * original {@link Operator}{@code } * @return {@link Operator}{@code } function that can be modified, decorated, replaced or just - * returned as a pass-thru + * returned as a pass through */ public Operator onLift(final Operator lift) { return lift; diff --git a/src/main/java/rx/plugins/RxJavaPlugins.java b/src/main/java/rx/plugins/RxJavaPlugins.java index 9678a32e15..6391a91185 100644 --- a/src/main/java/rx/plugins/RxJavaPlugins.java +++ b/src/main/java/rx/plugins/RxJavaPlugins.java @@ -36,7 +36,7 @@ * * Where the {@code .class} property contains the simple classname from above and the {@code .impl} * contains the fully qualified name of the implementation class. The {@code [index]} can be - * any short string or number of your chosing. For example, you can now define a custom + * any short string or number of your choosing. For example, you can now define a custom * {@code RxJavaErrorHandler} via two system property: *


  * rxjava.plugin.1.class=RxJavaErrorHandler
diff --git a/src/main/java/rx/plugins/RxJavaSchedulersHook.java b/src/main/java/rx/plugins/RxJavaSchedulersHook.java
index 3bf923464a..133cdc363a 100644
--- a/src/main/java/rx/plugins/RxJavaSchedulersHook.java
+++ b/src/main/java/rx/plugins/RxJavaSchedulersHook.java
@@ -71,7 +71,7 @@ public Scheduler getNewThreadScheduler() {
 
     /**
      * Invoked before the Action is handed over to the scheduler.  Can be used for wrapping/decorating/logging.
-     * The default is just a passthrough.
+     * The default is just a pass through.
      * @param action action to schedule
      * @return wrapped action to schedule
      */
diff --git a/src/main/java/rx/plugins/RxJavaSingleExecutionHook.java b/src/main/java/rx/plugins/RxJavaSingleExecutionHook.java
index 9fce6531f3..65c7ad3155 100644
--- a/src/main/java/rx/plugins/RxJavaSingleExecutionHook.java
+++ b/src/main/java/rx/plugins/RxJavaSingleExecutionHook.java
@@ -43,12 +43,12 @@ public abstract class RxJavaSingleExecutionHook {
      * Invoked during the construction by {@link Single#create(Single.OnSubscribe)}
      * 

* This can be used to decorate or replace the onSubscribe function or just perform extra - * logging, metrics and other such things and pass-thru the function. + * logging, metrics and other such things and pass through the function. * * @param f * original {@link Single.OnSubscribe}<{@code T}> to be executed * @return {@link Single.OnSubscribe}<{@code T}> function that can be modified, decorated, replaced or just - * returned as a pass-thru + * returned as a pass through */ public Single.OnSubscribe onCreate(Single.OnSubscribe f) { return f; @@ -58,15 +58,15 @@ public Single.OnSubscribe onCreate(Single.OnSubscribe f) { * Invoked before {@link Single#subscribe(Subscriber)} is about to be executed. *

* This can be used to decorate or replace the onSubscribe function or just perform extra - * logging, metrics and other such things and pass-thru the function. + * logging, metrics and other such things and pass through the function. * * @param onSubscribe * original {@link Observable.OnSubscribe}<{@code T}> to be executed * @return {@link Observable.OnSubscribe}<{@code T}> function that can be modified, decorated, replaced or just - * returned as a pass-thru + * returned as a pass through */ public Observable.OnSubscribe onSubscribeStart(Single singleInstance, final Observable.OnSubscribe onSubscribe) { - // pass-thru by default + // pass through by default return onSubscribe; } @@ -75,15 +75,15 @@ public Observable.OnSubscribe onSubscribeStart(Single single * {@link Subscription}. *

* This can be used to decorate or replace the {@link Subscription} instance or just perform extra logging, - * metrics and other such things and pass-thru the subscription. + * metrics and other such things and pass through the subscription. * * @param subscription * original {@link Subscription} * @return {@link Subscription} subscription that can be modified, decorated, replaced or just returned as a - * pass-thru + * pass through */ public Subscription onSubscribeReturn(Subscription subscription) { - // pass-thru by default + // pass through by default return subscription; } @@ -95,10 +95,10 @@ public Subscription onSubscribeReturn(Subscription subscription) { * * @param e * Throwable thrown by {@link Single#subscribe(Subscriber)} - * @return Throwable that can be decorated, replaced or just returned as a pass-thru + * @return Throwable that can be decorated, replaced or just returned as a pass through */ public Throwable onSubscribeError(Throwable e) { - // pass-thru by default + // pass through by default return e; } @@ -107,12 +107,12 @@ public Throwable onSubscribeError(Throwable e) { * {@link Single} and the return value is used as the lifted function *

* This can be used to decorate or replace the {@link Observable.Operator} instance or just perform extra - * logging, metrics and other such things and pass-thru the onSubscribe. + * logging, metrics and other such things and pass through the onSubscribe. * * @param lift * original {@link Observable.Operator}{@code } * @return {@link Observable.Operator}{@code } function that can be modified, decorated, replaced or just - * returned as a pass-thru + * returned as a pass through */ public Observable.Operator onLift(final Observable.Operator lift) { return lift; diff --git a/src/main/java/rx/schedulers/Schedulers.java b/src/main/java/rx/schedulers/Schedulers.java index 7dd8186616..0ec2d3a273 100644 --- a/src/main/java/rx/schedulers/Schedulers.java +++ b/src/main/java/rx/schedulers/Schedulers.java @@ -143,7 +143,7 @@ public static Scheduler from(Executor executor) { * Starts those standard Schedulers which support the SchedulerLifecycle interface. *

The operation is idempotent and threadsafe. */ - /* public testonly */ static void start() { + /* public test only */ static void start() { Schedulers s = INSTANCE; synchronized (s) { if (s.computationScheduler instanceof SchedulerLifecycle) { diff --git a/src/main/java/rx/subjects/ReplaySubject.java b/src/main/java/rx/subjects/ReplaySubject.java index 6b5b43c799..db8490f731 100644 --- a/src/main/java/rx/subjects/ReplaySubject.java +++ b/src/main/java/rx/subjects/ReplaySubject.java @@ -78,7 +78,7 @@ public static ReplaySubject create() { /** * Creates an unbounded replay subject with the specified initial buffer capacity. *

- * Use this method to avoid excessive array reallocation while the internal buffer grows to accomodate new + * Use this method to avoid excessive array reallocation while the internal buffer grows to accommodate new * items. For example, if you know that the buffer will hold 32k items, you can ask the * {@code ReplaySubject} to preallocate its internal array with a capacity to hold that many items. Once * the items start to arrive, the internal array won't need to grow, creating less garbage and no overhead @@ -525,7 +525,7 @@ public Integer replayObserverFromIndexTest(Integer idx, SubjectObserver 0) { Object o = list.get(idx - 1); if (nl.isCompleted(o) || nl.isError(o)) { diff --git a/src/main/java/rx/subjects/TestSubject.java b/src/main/java/rx/subjects/TestSubject.java index f7a0caee2a..db57feedd4 100644 --- a/src/main/java/rx/subjects/TestSubject.java +++ b/src/main/java/rx/subjects/TestSubject.java @@ -121,10 +121,10 @@ void _onError(final Throwable e) { * * @param e * the {@code Throwable} to pass to the {@code onError} method - * @param dalayTime + * @param delayTime * the number of milliseconds in the future relative to "now()" at which to call {@code onError} */ - public void onError(final Throwable e, long dalayTime) { + public void onError(final Throwable e, long delayTime) { innerScheduler.schedule(new Action0() { @Override @@ -132,7 +132,7 @@ public void call() { _onError(e); } - }, dalayTime, TimeUnit.MILLISECONDS); + }, delayTime, TimeUnit.MILLISECONDS); } /** diff --git a/src/test/java/rx/BackpressureTests.java b/src/test/java/rx/BackpressureTests.java index e46dfebcb5..393c8155cf 100644 --- a/src/test/java/rx/BackpressureTests.java +++ b/src/test/java/rx/BackpressureTests.java @@ -96,7 +96,7 @@ public void testMergeSync() { assertEquals(NUM, ts.getOnNextEvents().size()); // either one can starve the other, but neither should be capable of doing more than 5 batches (taking 4.1) // TODO is it possible to make this deterministic rather than one possibly starving the other? - // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair" algoritms generally take a performance hit + // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair" algorithms generally take a performance hit assertTrue(c1.get() < RxRingBuffer.SIZE * 5); assertTrue(c2.get() < RxRingBuffer.SIZE * 5); } @@ -118,7 +118,7 @@ public void testMergeAsync() { assertEquals(NUM, ts.getOnNextEvents().size()); // either one can starve the other, but neither should be capable of doing more than 5 batches (taking 4.1) // TODO is it possible to make this deterministic rather than one possibly starving the other? - // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair" algoritms generally take a performance hit + // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair" algorithms generally take a performance hit assertTrue(c1.get() < RxRingBuffer.SIZE * 5); assertTrue(c2.get() < RxRingBuffer.SIZE * 5); } @@ -164,7 +164,7 @@ public void testMergeAsyncThenObserveOn() { assertEquals(NUM, ts.getOnNextEvents().size()); // either one can starve the other, but neither should be capable of doing more than 5 batches (taking 4.1) // TODO is it possible to make this deterministic rather than one possibly starving the other? - // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair" algoritms generally take a performance hit + // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair" algorithms generally take a performance hit // akarnokd => run this in a loop over 10k times and never saw values get as high as 7*SIZE, but since observeOn delays the unsubscription non-deterministically, the test will remain unreliable assertTrue(c1.get() < RxRingBuffer.SIZE * 7); assertTrue(c2.get() < RxRingBuffer.SIZE * 7); diff --git a/src/test/java/rx/SingleTest.java b/src/test/java/rx/SingleTest.java index 24855bf415..2952e22bfd 100644 --- a/src/test/java/rx/SingleTest.java +++ b/src/test/java/rx/SingleTest.java @@ -1340,7 +1340,7 @@ public void onErrorResumeNextViaSingleShouldPreventNullSingle() { } @Test - public void onErrorResumeNextViaFunctionShouldNotInterruptSuccesfulSingle() { + public void onErrorResumeNextViaFunctionShouldNotInterruptSuccessfulSingle() { TestSubscriber testSubscriber = new TestSubscriber(); Single diff --git a/src/test/java/rx/SubscriberTest.java b/src/test/java/rx/SubscriberTest.java index 4b0f8f3f23..95d489e2c6 100644 --- a/src/test/java/rx/SubscriberTest.java +++ b/src/test/java/rx/SubscriberTest.java @@ -160,7 +160,7 @@ public void request(long n) { } }); - // this will be Long.MAX_VALUE because it is decoupled and nothing requsted on the Operator subscriber + // this will be Long.MAX_VALUE because it is decoupled and nothing requested on the Operator subscriber assertEquals(Long.MAX_VALUE, r.get()); } diff --git a/src/test/java/rx/exceptions/OnNextValueTest.java b/src/test/java/rx/exceptions/OnNextValueTest.java index b620e3eed0..00e5392dab 100644 --- a/src/test/java/rx/exceptions/OnNextValueTest.java +++ b/src/test/java/rx/exceptions/OnNextValueTest.java @@ -37,7 +37,7 @@ * this.value = value; * } * ``` - * I know this is probably a helpful error message in some cases but this can be a really costly operation when an objects toString is an expensive call or contains alot of output. I don't think we should be printing this in any case but if so it should be on demand (overload of getMessage()) rather than eagerly. + * I know this is probably a helpful error message in some cases but this can be a really costly operation when an objects toString is an expensive call or contains a lot of output. I don't think we should be printing this in any case but if so it should be on demand (overload of getMessage()) rather than eagerly. *

* In my case it is causing a toString of a large context object that is normally only used for debugging purposes which makes the exception logs hard to use and they are rolling over the log files very quickly. *

diff --git a/src/test/java/rx/exceptions/TestException.java b/src/test/java/rx/exceptions/TestException.java index 8d44a6f3af..16422b7f25 100644 --- a/src/test/java/rx/exceptions/TestException.java +++ b/src/test/java/rx/exceptions/TestException.java @@ -28,7 +28,7 @@ public TestException() { } /** * Create the test exception with the provided message. - * @param message the mesage to use + * @param message the message to use */ public TestException(String message) { super(message); diff --git a/src/test/java/rx/internal/operators/OnSubscribeUsingTest.java b/src/test/java/rx/internal/operators/OnSubscribeUsingTest.java index 0ee4192add..a68605dd8c 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeUsingTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeUsingTest.java @@ -108,7 +108,7 @@ public Observable call(Resource resource) { inOrder.verify(observer, times(1)).onCompleted(); inOrder.verifyNoMoreInteractions(); - // The resouce should be closed + // The resource should be closed verify(resource, times(1)).dispose(); } diff --git a/src/test/java/rx/internal/operators/OperatorConcatTest.java b/src/test/java/rx/internal/operators/OperatorConcatTest.java index a54c435432..65aa1f5307 100644 --- a/src/test/java/rx/internal/operators/OperatorConcatTest.java +++ b/src/test/java/rx/internal/operators/OperatorConcatTest.java @@ -421,7 +421,7 @@ public void testConcatUnsubscribe() { Subscription s1 = concat.subscribe(observer); //Block main thread to allow observable "w1" to complete and observable "w2" to call onNext once. callOnce.await(); - // Unsubcribe + // Unsubscribe s1.unsubscribe(); //Unblock the observable to continue. okToContinue.countDown(); diff --git a/src/test/java/rx/internal/operators/OperatorFlatMapTest.java b/src/test/java/rx/internal/operators/OperatorFlatMapTest.java index bb5127665c..56d733affd 100644 --- a/src/test/java/rx/internal/operators/OperatorFlatMapTest.java +++ b/src/test/java/rx/internal/operators/OperatorFlatMapTest.java @@ -324,7 +324,7 @@ public void call() { @Override public void call() { if (subscriptionCount.decrementAndGet() < 0) { - Assert.fail("Too many unsubscriptionss! " + subscriptionCount.get()); + Assert.fail("Too many unsubscriptions! " + subscriptionCount.get()); } } }); diff --git a/src/test/java/rx/internal/operators/OperatorOnBackpressureBufferTest.java b/src/test/java/rx/internal/operators/OperatorOnBackpressureBufferTest.java index 59a971e1c1..ac01daa591 100644 --- a/src/test/java/rx/internal/operators/OperatorOnBackpressureBufferTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnBackpressureBufferTest.java @@ -154,7 +154,7 @@ public void testFixBackpressureBoundedBufferDroppingOldest() } @Test - public void testFixBackpressueBoundedBufferDroppingLatest() + public void testFixBackpressureBoundedBufferDroppingLatest() throws InterruptedException { List events = overflowBufferWithBehaviour(100, 10, ON_OVERFLOW_DROP_LATEST); diff --git a/src/test/java/rx/internal/operators/OperatorPublishFunctionTest.java b/src/test/java/rx/internal/operators/OperatorPublishFunctionTest.java index 761dead9c5..0221c921ac 100644 --- a/src/test/java/rx/internal/operators/OperatorPublishFunctionTest.java +++ b/src/test/java/rx/internal/operators/OperatorPublishFunctionTest.java @@ -202,7 +202,7 @@ public Observable call(Observable o) { } @Test - public void oveflowMissingBackpressureException() { + public void overflowMissingBackpressureException() { TestSubscriber ts = TestSubscriber.create(0); PublishSubject ps = PublishSubject.create(); @@ -227,7 +227,7 @@ public Observable call(Observable o) { } @Test - public void oveflowMissingBackpressureExceptionDelayed() { + public void overflowMissingBackpressureExceptionDelayed() { TestSubscriber ts = TestSubscriber.create(0); PublishSubject ps = PublishSubject.create(); diff --git a/src/test/java/rx/internal/operators/OperatorRetryTest.java b/src/test/java/rx/internal/operators/OperatorRetryTest.java index dc6eb510a9..0941c2d9d4 100644 --- a/src/test/java/rx/internal/operators/OperatorRetryTest.java +++ b/src/test/java/rx/internal/operators/OperatorRetryTest.java @@ -598,7 +598,7 @@ public void call() { } } - /** Observer for listener on seperate thread */ + /** Observer for listener on separate thread */ static final class AsyncObserver implements Observer { protected CountDownLatch latch = new CountDownLatch(1); diff --git a/src/test/java/rx/internal/operators/OperatorTimeoutTests.java b/src/test/java/rx/internal/operators/OperatorTimeoutTests.java index f14901176a..4c2ae8c75b 100644 --- a/src/test/java/rx/internal/operators/OperatorTimeoutTests.java +++ b/src/test/java/rx/internal/operators/OperatorTimeoutTests.java @@ -109,7 +109,7 @@ public void shouldTimeoutIfSecondOnNextNotWithinTimeout() { } @Test - public void shouldCompleteIfUnderlyingComletes() { + public void shouldCompleteIfUnderlyingCompletes() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); Subscription subscription = withTimeout.subscribe(observer); diff --git a/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java b/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java index 4d2799c12b..51670b1b7a 100644 --- a/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java +++ b/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java @@ -426,7 +426,7 @@ public void run() { latchTimeout.set(true); } - assertFalse("CoundDownLatch timeout", latchTimeout.get()); + assertFalse("CountDownLatch timeout", latchTimeout.get()); InOrder inOrder = inOrder(o); inOrder.verify(o).onNext(1); diff --git a/src/test/java/rx/internal/operators/OperatorZipTest.java b/src/test/java/rx/internal/operators/OperatorZipTest.java index f13ba0ccb9..75f83f3e67 100644 --- a/src/test/java/rx/internal/operators/OperatorZipTest.java +++ b/src/test/java/rx/internal/operators/OperatorZipTest.java @@ -1241,7 +1241,7 @@ public Integer call(Integer i1, Integer i2) { assertEquals(expected, zip2.toList().toBlocking().single()); } @Test - public void testUnboundedDownstreamOverrequesting() { + public void testUnboundedDownstreamOverRequesting() { Observable source = Observable.range(1, 2).zipWith(Observable.range(1, 2), new Func2() { @Override public Integer call(Integer t1, Integer t2) { diff --git a/src/test/java/rx/internal/operators/SingleOnSubscribeUsingTest.java b/src/test/java/rx/internal/operators/SingleOnSubscribeUsingTest.java index 238f373115..96fd9ab05e 100644 --- a/src/test/java/rx/internal/operators/SingleOnSubscribeUsingTest.java +++ b/src/test/java/rx/internal/operators/SingleOnSubscribeUsingTest.java @@ -94,7 +94,7 @@ public Single call(Resource resource) { inOrder.verify(observer).onCompleted(); inOrder.verifyNoMoreInteractions(); - // The resouce should be closed + // The resource should be closed verify(resource).dispose(); } diff --git a/src/test/java/rx/schedulers/TrampolineSchedulerTest.java b/src/test/java/rx/schedulers/TrampolineSchedulerTest.java index a1bad56e34..aad14239a5 100644 --- a/src/test/java/rx/schedulers/TrampolineSchedulerTest.java +++ b/src/test/java/rx/schedulers/TrampolineSchedulerTest.java @@ -86,7 +86,7 @@ public void call() { @Override public void call() { workers.add(doWorkOnNewTrampoline("B", workDone)); - // we unsubscribe worker2 ... it should not affect work scheduled on a separate Trampline.Worker + // we unsubscribe worker2 ... it should not affect work scheduled on a separate Trampoline.Worker worker2.unsubscribe(); } diff --git a/src/test/java/rx/subjects/ReplaySubjectBoundedConcurrencyTest.java b/src/test/java/rx/subjects/ReplaySubjectBoundedConcurrencyTest.java index 2f7461d2d5..8cbe051594 100644 --- a/src/test/java/rx/subjects/ReplaySubjectBoundedConcurrencyTest.java +++ b/src/test/java/rx/subjects/ReplaySubjectBoundedConcurrencyTest.java @@ -449,7 +449,7 @@ public void run() { Assert.fail("Size decreased! " + lastSize + " -> " + size); } if ((size > 0) && !hasAny) { - Assert.fail("hasAnyValue reports emptyness but size doesn't"); + Assert.fail("hasAnyValue reports emptiness but size doesn't"); } if (size > values.length) { Assert.fail("Got fewer values than size! " + size + " -> " + values.length); diff --git a/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java b/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java index a90c5ac0f3..e0744ef1d2 100644 --- a/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java +++ b/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java @@ -446,7 +446,7 @@ public void run() { Assert.fail("Size decreased! " + lastSize + " -> " + size); } if ((size > 0) && !hasAny) { - Assert.fail("hasAnyValue reports emptyness but size doesn't"); + Assert.fail("hasAnyValue reports emptiness but size doesn't"); } if (size > values.length) { Assert.fail("Got fewer values than size! " + size + " -> " + values.length); diff --git a/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java b/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java index 18ac45f198..dacb8e11f0 100644 --- a/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java +++ b/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java @@ -335,7 +335,7 @@ public void testTryRemoveIfNotIn() { csub.remove(csub1); csub.add(csub2); - csub.remove(csub1); // try removing agian + csub.remove(csub1); // try removing again } @Test(expected = NullPointerException.class) diff --git a/src/test/java/rx/subscriptions/SerialSubscriptionTests.java b/src/test/java/rx/subscriptions/SerialSubscriptionTests.java index 8c4482ce54..8e1d37fe2d 100644 --- a/src/test/java/rx/subscriptions/SerialSubscriptionTests.java +++ b/src/test/java/rx/subscriptions/SerialSubscriptionTests.java @@ -48,7 +48,7 @@ public void unsubscribingWithoutUnderlyingDoesNothing() { } @Test - public void getSubscriptionShouldReturnset() { + public void getSubscriptionShouldReturnSet() { final Subscription underlying = mock(Subscription.class); serialSubscription.set(underlying); assertSame(underlying, serialSubscription.get()); diff --git a/src/test/java/rx/test/TestObstructionDetection.java b/src/test/java/rx/test/TestObstructionDetection.java index 859d138dd0..d24c6721ab 100644 --- a/src/test/java/rx/test/TestObstructionDetection.java +++ b/src/test/java/rx/test/TestObstructionDetection.java @@ -45,7 +45,7 @@ private TestObstructionDetection() { } /** * Checks if tasks can be immediately executed on the computation scheduler. - * @throws ObstructionExceptio if the schedulers don't respond within 1 second + * @throws ObstructionException if the schedulers don't respond within 1 second */ public static void checkObstruction() { final int ncpu = Runtime.getRuntime().availableProcessors(); From db2e23274f9585add9469fc4305ab1c16bfbee3d Mon Sep 17 00:00:00 2001 From: Prat Date: Sat, 26 Mar 2016 23:43:07 -0400 Subject: [PATCH 002/294] 1.x: Add Completable.andThen(Single) --- src/main/java/rx/Completable.java | 18 +++ src/main/java/rx/Single.java | 25 +++- ...ngleOnSubscribeDelaySubscriptionOther.java | 91 ++++++++++++++ src/test/java/rx/CompletableTest.java | 55 +++++++++ ...OnSubscribeDelaySubscriptionOtherTest.java | 112 ++++++++++++++++++ 5 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 src/main/java/rx/internal/operators/SingleOnSubscribeDelaySubscriptionOther.java create mode 100644 src/test/java/rx/internal/operators/SingleOnSubscribeDelaySubscriptionOtherTest.java diff --git a/src/main/java/rx/Completable.java b/src/main/java/rx/Completable.java index f2e752c2b2..357969565d 100644 --- a/src/main/java/rx/Completable.java +++ b/src/main/java/rx/Completable.java @@ -1114,6 +1114,24 @@ public final Observable andThen(Observable next) { requireNonNull(next); return next.delaySubscription(toObservable()); } + + /** + * Returns a Single which will subscribe to this Completable and once that is completed then + * will subscribe to the {@code next} Single. An error event from this Completable will be + * propagated to the downstream subscriber and will result in skipping the subscription of the + * Single. + *

+ *
Scheduler:
+ *
{@code andThen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param next the Single to subscribe after this Completable is completed, not null + * @return Single that composes this Completable and next + */ + public final Single andThen(Single next) { + requireNonNull(next); + return next.delaySubscription(toObservable()); + } /** * Concatenates this Completable with another Completable. diff --git a/src/main/java/rx/Single.java b/src/main/java/rx/Single.java index f5e1156acc..a798073ff9 100644 --- a/src/main/java/rx/Single.java +++ b/src/main/java/rx/Single.java @@ -27,7 +27,6 @@ import rx.internal.util.UtilityFunctions; import rx.observers.SafeSubscriber; import rx.observers.SerializedSubscriber; -import rx.plugins.RxJavaObservableExecutionHook; import rx.plugins.RxJavaPlugins; import rx.plugins.RxJavaSingleExecutionHook; import rx.schedulers.Schedulers; @@ -2671,4 +2670,28 @@ public static Single using( return create(new SingleOnSubscribeUsing(resourceFactory, singleFactory, disposeAction, disposeEagerly)); } + /** + * Returns a Single that delays the subscription to this Single + * until the Observable completes. In case the {@code onError} of the supplied observer throws, + * the exception will be propagated to the downstream subscriber + * and will result in skipping the subscription of this Single. + * + *

+ *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other the Observable that should trigger the subscription + * to this Single. + * @return a Single that delays the subscription to this Single + * until the Observable emits an element or completes normally. + */ + @Experimental + public final Single delaySubscription(Observable other) { + if (other == null) { + throw new NullPointerException(); + } + return create(new SingleOnSubscribeDelaySubscriptionOther(this, other)); + } } diff --git a/src/main/java/rx/internal/operators/SingleOnSubscribeDelaySubscriptionOther.java b/src/main/java/rx/internal/operators/SingleOnSubscribeDelaySubscriptionOther.java new file mode 100644 index 0000000000..efd9cf517b --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleOnSubscribeDelaySubscriptionOther.java @@ -0,0 +1,91 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package rx.internal.operators; + +import rx.Observable; +import rx.Single; +import rx.SingleSubscriber; +import rx.Subscriber; +import rx.plugins.RxJavaPlugins; +import rx.subscriptions.SerialSubscription; + +/** + * Delays the subscription to the Single until the Observable + * fires an event or completes. + * + * @param the Single value type + */ +public final class SingleOnSubscribeDelaySubscriptionOther implements Single.OnSubscribe { + final Single main; + final Observable other; + + public SingleOnSubscribeDelaySubscriptionOther(Single main, Observable other) { + this.main = main; + this.other = other; + } + + @Override + public void call(final SingleSubscriber subscriber) { + final SingleSubscriber child = new SingleSubscriber() { + @Override + public void onSuccess(T value) { + subscriber.onSuccess(value); + } + + @Override + public void onError(Throwable error) { + subscriber.onError(error); + } + }; + + final SerialSubscription serial = new SerialSubscription(); + subscriber.add(serial); + + Subscriber otherSubscriber = new Subscriber() { + boolean done; + @Override + public void onNext(Object t) { + onCompleted(); + } + + @Override + public void onError(Throwable e) { + if (done) { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + return; + } + done = true; + child.onError(e); + } + + @Override + public void onCompleted() { + if (done) { + return; + } + done = true; + serial.set(child); + + main.subscribe(child); + } + }; + + serial.set(otherSubscriber); + + other.subscribe(otherSubscriber); + } +} diff --git a/src/test/java/rx/CompletableTest.java b/src/test/java/rx/CompletableTest.java index 894c72109f..2493da2356 100644 --- a/src/test/java/rx/CompletableTest.java +++ b/src/test/java/rx/CompletableTest.java @@ -418,6 +418,61 @@ public void andThenSubscribeOn() { ts.assertCompleted(); ts.assertNoErrors(); } + + @Test + public void andThenSingle() { + TestSubscriber ts = new TestSubscriber(0); + Completable.complete().andThen(Single.just("foo")).subscribe(ts); + ts.requestMore(1); + ts.assertValue("foo"); + ts.assertCompleted(); + ts.assertNoErrors(); + ts.assertUnsubscribed(); + } + + @Test + public void andThenSingleNever() { + TestSubscriber ts = new TestSubscriber(0); + Completable.never().andThen(Single.just("foo")).subscribe(ts); + ts.requestMore(1); + ts.assertNoValues(); + ts.assertNoTerminalEvent(); + } + + @Test + public void andThenSingleError() { + TestSubscriber ts = new TestSubscriber(0); + final AtomicBoolean hasRun = new AtomicBoolean(false); + final Exception e = new Exception(); + Completable.error(e) + .andThen(Single.create(new Single.OnSubscribe() { + @Override + public void call(SingleSubscriber s) { + hasRun.set(true); + s.onSuccess("foo"); + } + })) + .subscribe(ts); + ts.assertNoValues(); + ts.assertError(e); + ts.assertUnsubscribed(); + Assert.assertFalse("Should not have subscribed to single when completable errors", hasRun.get()); + } + + @Test + public void andThenSingleSubscribeOn() { + TestSubscriber ts = new TestSubscriber(0); + TestScheduler scheduler = new TestScheduler(); + Completable.complete().andThen(Single.just("foo").delay(1, TimeUnit.SECONDS, scheduler)).subscribe(ts); + ts.requestMore(1); + ts.assertNoValues(); + ts.assertNoTerminalEvent(); + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + ts.assertValue("foo"); + ts.assertCompleted(); + ts.assertNoErrors(); + ts.assertUnsubscribed(); + } @Test(expected = NullPointerException.class) public void createNull() { diff --git a/src/test/java/rx/internal/operators/SingleOnSubscribeDelaySubscriptionOtherTest.java b/src/test/java/rx/internal/operators/SingleOnSubscribeDelaySubscriptionOtherTest.java new file mode 100644 index 0000000000..c17f3b6e1f --- /dev/null +++ b/src/test/java/rx/internal/operators/SingleOnSubscribeDelaySubscriptionOtherTest.java @@ -0,0 +1,112 @@ +package rx.internal.operators; + +import org.junit.Assert; +import org.junit.Test; +import rx.Single; +import rx.exceptions.TestException; +import rx.functions.Action0; +import rx.observers.TestSubscriber; +import rx.subjects.PublishSubject; + +import java.util.concurrent.atomic.AtomicInteger; + +public class SingleOnSubscribeDelaySubscriptionOtherTest { + @Test + public void noPrematureSubscription() { + PublishSubject other = PublishSubject.create(); + + TestSubscriber ts = TestSubscriber.create(); + + final AtomicInteger subscribed = new AtomicInteger(); + + Single.just(1) + .doOnSubscribe(new Action0() { + @Override + public void call() { + subscribed.getAndIncrement(); + } + }) + .delaySubscription(other) + .subscribe(ts); + + ts.assertNotCompleted(); + ts.assertNoErrors(); + ts.assertNoValues(); + + Assert.assertEquals("Premature subscription", 0, subscribed.get()); + + other.onNext(1); + + Assert.assertEquals("No subscription", 1, subscribed.get()); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void noPrematureSubscriptionToError() { + PublishSubject other = PublishSubject.create(); + + TestSubscriber ts = TestSubscriber.create(); + + final AtomicInteger subscribed = new AtomicInteger(); + + Single.error(new TestException()) + .doOnSubscribe(new Action0() { + @Override + public void call() { + subscribed.getAndIncrement(); + } + }) + .delaySubscription(other) + .subscribe(ts); + + ts.assertNotCompleted(); + ts.assertNoErrors(); + ts.assertNoValues(); + + Assert.assertEquals("Premature subscription", 0, subscribed.get()); + + other.onNext(1); + + Assert.assertEquals("No subscription", 1, subscribed.get()); + + ts.assertNoValues(); + ts.assertNotCompleted(); + ts.assertError(TestException.class); + } + + @Test + public void noSubscriptionIfOtherErrors() { + PublishSubject other = PublishSubject.create(); + + TestSubscriber ts = TestSubscriber.create(); + + final AtomicInteger subscribed = new AtomicInteger(); + + Single.error(new TestException()) + .doOnSubscribe(new Action0() { + @Override + public void call() { + subscribed.getAndIncrement(); + } + }) + .delaySubscription(other) + .subscribe(ts); + + ts.assertNotCompleted(); + ts.assertNoErrors(); + ts.assertNoValues(); + + Assert.assertEquals("Premature subscription", 0, subscribed.get()); + + other.onError(new TestException()); + + Assert.assertEquals("Premature subscription", 0, subscribed.get()); + + ts.assertNoValues(); + ts.assertNotCompleted(); + ts.assertError(TestException.class); + } +} From 47eb3061b7da7fab6e1c87b242fa604a6c0df0e7 Mon Sep 17 00:00:00 2001 From: akarnokd Date: Tue, 29 Mar 2016 17:33:24 +0200 Subject: [PATCH 003/294] 1.x: fix merge/flatMap crashing on an inner scalar of null --- .../rx/internal/operators/OperatorMerge.java | 2 +- .../internal/operators/OperatorMergeTest.java | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/rx/internal/operators/OperatorMerge.java b/src/main/java/rx/internal/operators/OperatorMerge.java index a9c7b86b09..2aede6d9d7 100644 --- a/src/main/java/rx/internal/operators/OperatorMerge.java +++ b/src/main/java/rx/internal/operators/OperatorMerge.java @@ -475,7 +475,7 @@ protected void queueScalar(T value) { } this.queue = q; } - if (!q.offer(value)) { + if (!q.offer(nl.next(value))) { unsubscribe(); onError(OnErrorThrowable.addValueAsLastCause(new MissingBackpressureException(), value)); return; diff --git a/src/test/java/rx/internal/operators/OperatorMergeTest.java b/src/test/java/rx/internal/operators/OperatorMergeTest.java index 2c40ac53d3..b93f32b580 100644 --- a/src/test/java/rx/internal/operators/OperatorMergeTest.java +++ b/src/test/java/rx/internal/operators/OperatorMergeTest.java @@ -28,10 +28,10 @@ import org.mockito.*; import rx.*; -import rx.Observable.OnSubscribe; -import rx.Scheduler.Worker; import rx.Observable; +import rx.Observable.OnSubscribe; import rx.Observer; +import rx.Scheduler.Worker; import rx.functions.*; import rx.internal.util.RxRingBuffer; import rx.observers.TestSubscriber; @@ -1353,4 +1353,21 @@ public void zeroMaxConcurrent() { assertEquals("maxConcurrent > 0 required but it was 0", e.getMessage()); } } + + @Test + public void mergeJustNull() { + TestSubscriber ts = new TestSubscriber(0); + + Observable.range(1, 2).flatMap(new Func1>() { + @Override + public Observable call(Integer t) { + return Observable.just(null); + } + }).subscribe(ts); + + ts.requestMore(2); + ts.assertValues(null, null); + ts.assertNoErrors(); + ts.assertCompleted(); + } } From f33873e79797895d27b20d64e8112f2fa38ee9f0 Mon Sep 17 00:00:00 2001 From: Aaron Tull Date: Thu, 31 Mar 2016 12:51:24 -0700 Subject: [PATCH 004/294] Upgrading SyncOnSubscribe from Experimental to Beta --- src/main/java/rx/Observable.java | 2 +- src/main/java/rx/observables/SyncOnSubscribe.java | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/rx/Observable.java b/src/main/java/rx/Observable.java index 74fcf88fe9..b634c92215 100644 --- a/src/main/java/rx/Observable.java +++ b/src/main/java/rx/Observable.java @@ -126,7 +126,7 @@ public static Observable create(OnSubscribe f) { * @see ReactiveX operators documentation: Create * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) */ - @Experimental + @Beta public static Observable create(SyncOnSubscribe syncOnSubscribe) { return new Observable(hook.onCreate(syncOnSubscribe)); } diff --git a/src/main/java/rx/observables/SyncOnSubscribe.java b/src/main/java/rx/observables/SyncOnSubscribe.java index 910a5acddb..c2ff69e6e8 100644 --- a/src/main/java/rx/observables/SyncOnSubscribe.java +++ b/src/main/java/rx/observables/SyncOnSubscribe.java @@ -23,6 +23,7 @@ import rx.Producer; import rx.Subscriber; import rx.Subscription; +import rx.annotations.Beta; import rx.annotations.Experimental; import rx.exceptions.Exceptions; import rx.functions.Action0; @@ -46,7 +47,7 @@ * @param * the type of {@code Subscribers} that will be compatible with {@code this}. */ -@Experimental +@Beta public abstract class SyncOnSubscribe implements OnSubscribe { /* (non-Javadoc) @@ -126,7 +127,7 @@ protected void onUnsubscribe(S state) { * next(S, Subscriber)}) * @return a SyncOnSubscribe that emits data in a protocol compatible with back-pressure. */ - @Experimental + @Beta public static SyncOnSubscribe createSingleState(Func0 generator, final Action2> next) { Func2, S> nextFunc = new Func2, S>() { @@ -155,7 +156,7 @@ public S call(S state, Observer subscriber) { * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental + @Beta public static SyncOnSubscribe createSingleState(Func0 generator, final Action2> next, final Action1 onUnsubscribe) { @@ -183,7 +184,7 @@ public S call(S state, Observer subscriber) { * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental + @Beta public static SyncOnSubscribe createStateful(Func0 generator, Func2, ? extends S> next, Action1 onUnsubscribe) { @@ -202,7 +203,7 @@ public static SyncOnSubscribe createStateful(Func0 gen * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental + @Beta public static SyncOnSubscribe createStateful(Func0 generator, Func2, ? extends S> next) { return new SyncOnSubscribeImpl(generator, next); @@ -221,7 +222,7 @@ public static SyncOnSubscribe createStateful(Func0 gen * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental + @Beta public static SyncOnSubscribe createStateless(final Action1> next) { Func2, Void> nextFunc = new Func2, Void>() { @Override @@ -248,7 +249,7 @@ public Void call(Void state, Observer subscriber) { * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental + @Beta public static SyncOnSubscribe createStateless(final Action1> next, final Action0 onUnsubscribe) { Func2, Void> nextFunc = new Func2, Void>() { From d9ac3b8a577d4130cd9a47992ca8d7421700549c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Karnok?= Date: Sun, 3 Apr 2016 03:56:26 +0200 Subject: [PATCH 005/294] 1.x: Fix ObserveOnTest.testQueueFullEmitsErrorWithVaryingBufferSize --- .../operators/OperatorObserveOnTest.java | 74 +++++-------------- 1 file changed, 19 insertions(+), 55 deletions(-) diff --git a/src/test/java/rx/internal/operators/OperatorObserveOnTest.java b/src/test/java/rx/internal/operators/OperatorObserveOnTest.java index 8ebc69eed7..323f74b786 100644 --- a/src/test/java/rx/internal/operators/OperatorObserveOnTest.java +++ b/src/test/java/rx/internal/operators/OperatorObserveOnTest.java @@ -582,64 +582,28 @@ public void onNext(Integer t) { @Test public void testQueueFullEmitsErrorWithVaryingBufferSize() { - final CountDownLatch latch = new CountDownLatch(1); - // randomize buffer size, note that underlying implementations may be tuning the real size to a power of 2 - // which can lead to unexpected results when adding excess capacity (e.g.: see ConcurrentCircularArrayQueue) for (int i = 1; i <= 1024; i = i * 2) { final int capacity = i; - Observable observable = Observable.create(new OnSubscribe() { - - @Override - public void call(Subscriber o) { - for (int i = 0; i < capacity + 10; i++) { - o.onNext(i); - } - latch.countDown(); - o.onCompleted(); - } - - }); - - TestSubscriber testSubscriber = new TestSubscriber(new Observer() { - - @Override - public void onCompleted() { - - } - - @Override - public void onError(Throwable e) { - - } - - @Override - public void onNext(Integer t) { - try { - // force it to be slow wait until we have queued everything - latch.await(500, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - }); - System.out.println("Using capacity " + capacity); // for post-failure debugging - observable.observeOn(Schedulers.newThread(), capacity).subscribe(testSubscriber); - - testSubscriber.awaitTerminalEvent(); - List errors = testSubscriber.getOnErrorEvents(); - assertEquals(1, errors.size()); - System.out.println("Errors: " + errors); - Throwable t = errors.get(0); - if (t instanceof MissingBackpressureException) { - // success, we expect this - } else { - if (t.getCause() instanceof MissingBackpressureException) { - // this is also okay - } else { - fail("Expecting MissingBackpressureException"); - } + System.out.println(">> testQueueFullEmitsErrorWithVaryingBufferSize @ " + i); + + PublishSubject ps = PublishSubject.create(); + + TestSubscriber ts = new TestSubscriber(0); + + TestScheduler test = Schedulers.test(); + + ps.observeOn(test, capacity).subscribe(ts); + + for (int j = 0; j < capacity + 10; j++) { + ps.onNext(j); } + ps.onCompleted(); + + test.advanceTimeBy(1, TimeUnit.SECONDS); + + ts.assertNoValues(); + ts.assertError(MissingBackpressureException.class); + ts.assertNotCompleted(); } } From e971c4aa2ec3ae2c16e771b712f66cc23c95fdda Mon Sep 17 00:00:00 2001 From: Prat Date: Mon, 4 Apr 2016 03:01:33 -0400 Subject: [PATCH 006/294] 1.x: Fix TestSubscriber.create doc --- src/main/java/rx/observers/TestSubscriber.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/rx/observers/TestSubscriber.java b/src/main/java/rx/observers/TestSubscriber.java index 17655ab91f..887bd2998b 100644 --- a/src/main/java/rx/observers/TestSubscriber.java +++ b/src/main/java/rx/observers/TestSubscriber.java @@ -145,12 +145,12 @@ public static TestSubscriber create(long initialRequest) { public static TestSubscriber create(Observer delegate, long initialRequest) { return new TestSubscriber(delegate, initialRequest); } - + /** - * Factory method to construct a TestSubscriber which delegates events to the given Observer and + * Factory method to construct a TestSubscriber which delegates events to the given Subscriber and * an issues an initial request of Long.MAX_VALUE. * @param the value type - * @param delegate the observer to delegate events to + * @param delegate the subscriber to delegate events to * @return the created TestSubscriber instance * @throws NullPointerException if delegate is null * @since 1.1.0 @@ -160,10 +160,10 @@ public static TestSubscriber create(Subscriber delegate) { } /** - * Factory method to construct a TestSubscriber which delegates events to the given Subscriber and + * Factory method to construct a TestSubscriber which delegates events to the given Observer and * an issues an initial request of Long.MAX_VALUE. * @param the value type - * @param delegate the subscriber to delegate events to + * @param delegate the observer to delegate events to * @return the created TestSubscriber instance * @throws NullPointerException if delegate is null * @since 1.1.0 From 3e2dd9e60b4fa271a80156a9953e3c9ff3853798 Mon Sep 17 00:00:00 2001 From: Artem Zinnatullin Date: Mon, 4 Apr 2016 17:43:04 +0300 Subject: [PATCH 007/294] 1.x: Add system property for disabling usage of Unsafe API --- src/main/java/rx/internal/util/unsafe/UnsafeAccess.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java b/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java index a13989f4f1..076112982f 100644 --- a/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java +++ b/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java @@ -22,6 +22,9 @@ /** * All use of this class MUST first check that UnsafeAccess.isUnsafeAvailable() == true * otherwise NPEs will happen in environments without "suc.misc.Unsafe" such as Android. + *

+ * Note that you can force RxJava to not use Unsafe API by setting any value to System Property + * {@code rx.unsafe-disable}. */ public final class UnsafeAccess { private UnsafeAccess() { @@ -29,6 +32,9 @@ private UnsafeAccess() { } public static final Unsafe UNSAFE; + + private static final boolean DISABLED_BY_USER = System.getProperty("rx.unsafe-disable") != null; + static { Unsafe u = null; try { @@ -48,7 +54,7 @@ private UnsafeAccess() { } public static boolean isUnsafeAvailable() { - return UNSAFE != null; + return UNSAFE != null && !DISABLED_BY_USER; } /* From e26096d27c218242a8331aef93ace7a0ce989093 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Mon, 4 Apr 2016 19:50:32 +0200 Subject: [PATCH 008/294] 1.x: AsyncSubject now supports backpressure --- src/main/java/rx/subjects/AsyncSubject.java | 12 ++++-- .../subjects/SubjectSubscriptionManager.java | 4 +- .../java/rx/subjects/AsyncSubjectTest.java | 43 +++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/main/java/rx/subjects/AsyncSubject.java b/src/main/java/rx/subjects/AsyncSubject.java index 57539fa8eb..dac007f03f 100644 --- a/src/main/java/rx/subjects/AsyncSubject.java +++ b/src/main/java/rx/subjects/AsyncSubject.java @@ -22,6 +22,7 @@ import rx.exceptions.Exceptions; import rx.functions.Action1; import rx.internal.operators.NotificationLite; +import rx.internal.producers.SingleProducer; import rx.subjects.SubjectSubscriptionManager.SubjectObserver; /** @@ -68,9 +69,13 @@ public static AsyncSubject create() { public void call(SubjectObserver o) { Object v = state.getLatest(); NotificationLite nl = state.nl; - o.accept(v, nl); - if (v == null || (!nl.isCompleted(v) && !nl.isError(v))) { + if (v == null || nl.isCompleted(v)) { o.onCompleted(); + } else + if (nl.isError(v)) { + o.onError(nl.getError(v)); + } else { + o.actual.setProducer(new SingleProducer(o.actual, nl.getValue(v))); } } }; @@ -97,8 +102,7 @@ public void onCompleted() { if (last == nl.completed()) { bo.onCompleted(); } else { - bo.onNext(nl.getValue(last)); - bo.onCompleted(); + bo.actual.setProducer(new SingleProducer(bo.actual, nl.getValue(last))); } } } diff --git a/src/main/java/rx/subjects/SubjectSubscriptionManager.java b/src/main/java/rx/subjects/SubjectSubscriptionManager.java index 9a0c90ece7..8ac2d59e98 100644 --- a/src/main/java/rx/subjects/SubjectSubscriptionManager.java +++ b/src/main/java/rx/subjects/SubjectSubscriptionManager.java @@ -203,7 +203,7 @@ public State remove(SubjectObserver o) { */ protected static final class SubjectObserver implements Observer { /** The actual Observer. */ - final Observer actual; + final Subscriber actual; /** Was the emitFirst run? Guarded by this. */ boolean first = true; /** Guarded by this. */ @@ -215,7 +215,7 @@ protected static final class SubjectObserver implements Observer { protected volatile boolean caughtUp; /** Indicate where the observer is at replaying. */ private volatile Object index; - public SubjectObserver(Observer actual) { + public SubjectObserver(Subscriber actual) { this.actual = actual; } @Override diff --git a/src/test/java/rx/subjects/AsyncSubjectTest.java b/src/test/java/rx/subjects/AsyncSubjectTest.java index 968e71f571..82f6843f90 100644 --- a/src/test/java/rx/subjects/AsyncSubjectTest.java +++ b/src/test/java/rx/subjects/AsyncSubjectTest.java @@ -430,4 +430,47 @@ public void testAsyncSubjectValueError() { assertNull(async.getValue()); assertFalse(async.hasValue()); } + + @Test + public void backpressureOnline() { + TestSubscriber ts = TestSubscriber.create(0); + + AsyncSubject subject = AsyncSubject.create(); + + subject.subscribe(ts); + + subject.onNext(1); + subject.onCompleted(); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + + ts.requestMore(1); + + ts.assertValue(1); + ts.assertCompleted(); + ts.assertNoErrors(); + } + + @Test + public void backpressureOffline() { + TestSubscriber ts = TestSubscriber.create(0); + + AsyncSubject subject = AsyncSubject.create(); + subject.onNext(1); + subject.onCompleted(); + + subject.subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + + ts.requestMore(1); + + ts.assertValue(1); + ts.assertCompleted(); + ts.assertNoErrors(); + } } From 4dad04a9f5d8404f28345dc9abd2b971e93e6100 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Mon, 4 Apr 2016 20:11:37 +0200 Subject: [PATCH 009/294] 1.x: DoAfterTerminate handle if action throws --- .../operators/OperatorDoAfterTerminate.java | 13 ++++- .../OperatorDoAfterTerminateTest.java | 47 +++++++++++++++---- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/main/java/rx/internal/operators/OperatorDoAfterTerminate.java b/src/main/java/rx/internal/operators/OperatorDoAfterTerminate.java index a56d28795c..64afca478a 100644 --- a/src/main/java/rx/internal/operators/OperatorDoAfterTerminate.java +++ b/src/main/java/rx/internal/operators/OperatorDoAfterTerminate.java @@ -17,7 +17,9 @@ import rx.Observable.Operator; import rx.Subscriber; +import rx.exceptions.Exceptions; import rx.functions.Action0; +import rx.plugins.RxJavaPlugins; /** * Registers an action to be called after an Observable invokes {@code onComplete} or {@code onError}. @@ -53,7 +55,7 @@ public void onError(Throwable e) { try { child.onError(e); } finally { - action.call(); + callAction(); } } @@ -62,7 +64,16 @@ public void onCompleted() { try { child.onCompleted(); } finally { + callAction(); + } + } + + void callAction() { + try { action.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.getInstance().getErrorHandler().handleError(ex); } } }; diff --git a/src/test/java/rx/internal/operators/OperatorDoAfterTerminateTest.java b/src/test/java/rx/internal/operators/OperatorDoAfterTerminateTest.java index 6295386ae1..397451161d 100644 --- a/src/test/java/rx/internal/operators/OperatorDoAfterTerminateTest.java +++ b/src/test/java/rx/internal/operators/OperatorDoAfterTerminateTest.java @@ -15,18 +15,14 @@ */ package rx.internal.operators; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; -import rx.Observable; -import rx.Observer; +import rx.*; import rx.functions.Action0; +import rx.observers.TestSubscriber; public class OperatorDoAfterTerminateTest { @@ -65,4 +61,37 @@ public void nullActionShouldBeCheckedInConstructor() { assertEquals("Action can not be null", expected.getMessage()); } } + + @Test + public void nullFinallyActionShouldBeCheckedASAP() { + try { + Observable + .just("value") + .doAfterTerminate(null); + + fail(); + } catch (NullPointerException expected) { + + } + } + + @Test + public void ifFinallyActionThrowsExceptionShouldNotBeSwallowedAndActionShouldBeCalledOnce() { + Action0 finallyAction = mock(Action0.class); + doThrow(new IllegalStateException()).when(finallyAction).call(); + + TestSubscriber testSubscriber = new TestSubscriber(); + + Observable + .just("value") + .doAfterTerminate(finallyAction) + .subscribe(testSubscriber); + + testSubscriber.assertValue("value"); + + verify(finallyAction).call(); + // Actual result: + // Not only IllegalStateException was swallowed + // But finallyAction was called twice! + } } From 3c86972d6630653a81c9764c84856ed6786513bf Mon Sep 17 00:00:00 2001 From: David Karnok Date: Mon, 4 Apr 2016 20:13:48 +0200 Subject: [PATCH 010/294] 1.x: javac 9 compatibility fixes --- src/main/java/rx/Observable.java | 35 ++++++++++--------- src/main/java/rx/Single.java | 29 +++++++-------- .../operators/BlockingOperatorLatest.java | 2 +- .../operators/BlockingOperatorMostRecent.java | 2 +- .../operators/BlockingOperatorNext.java | 2 +- .../operators/BlockingOperatorToFuture.java | 2 +- .../operators/BlockingOperatorToIterator.java | 2 +- .../internal/operators/CachedObservable.java | 2 +- .../CompletableOnSubscribeConcat.java | 4 +-- .../CompletableOnSubscribeMerge.java | 4 +-- .../operators/OnSubscribeCombineLatest.java | 2 +- .../internal/operators/OperatorMapPair.java | 2 +- .../internal/operators/OperatorMulticast.java | 2 +- .../rx/internal/operators/OperatorReplay.java | 4 +-- ...ngleOnSubscribeDelaySubscriptionOther.java | 2 +- .../java/rx/observables/AsyncOnSubscribe.java | 7 ++-- .../rx/observables/BlockingObservable.java | 18 +++++----- src/main/java/rx/singles/BlockingSingle.java | 2 +- 18 files changed, 63 insertions(+), 60 deletions(-) diff --git a/src/main/java/rx/Observable.java b/src/main/java/rx/Observable.java index b634c92215..439da019ed 100644 --- a/src/main/java/rx/Observable.java +++ b/src/main/java/rx/Observable.java @@ -609,7 +609,7 @@ public static Observable amb(Observable o1, Observable Observable combineLatest(Observable o1, Observable o2, Func2 combineFunction) { - return combineLatest(Arrays.asList(o1, o2), Functions.fromFunc(combineFunction)); + return (Observable)combineLatest(Arrays.asList(o1, o2), Functions.fromFunc(combineFunction)); } /** @@ -637,7 +637,7 @@ public static Observable combineLatest(Observable o */ @SuppressWarnings("unchecked") public static Observable combineLatest(Observable o1, Observable o2, Observable o3, Func3 combineFunction) { - return combineLatest(Arrays.asList(o1, o2, o3), Functions.fromFunc(combineFunction)); + return (Observable)combineLatest(Arrays.asList(o1, o2, o3), Functions.fromFunc(combineFunction)); } /** @@ -668,7 +668,7 @@ public static Observable combineLatest(Observable Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Func4 combineFunction) { - return combineLatest(Arrays.asList(o1, o2, o3, o4), Functions.fromFunc(combineFunction)); + return (Observable)combineLatest(Arrays.asList(o1, o2, o3, o4), Functions.fromFunc(combineFunction)); } /** @@ -701,7 +701,7 @@ public static Observable combineLatest(Observable Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Func5 combineFunction) { - return combineLatest(Arrays.asList(o1, o2, o3, o4, o5), Functions.fromFunc(combineFunction)); + return (Observable)combineLatest(Arrays.asList(o1, o2, o3, o4, o5), Functions.fromFunc(combineFunction)); } /** @@ -736,7 +736,7 @@ public static Observable combineLatest(Observable Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Func6 combineFunction) { - return combineLatest(Arrays.asList(o1, o2, o3, o4, o5, o6), Functions.fromFunc(combineFunction)); + return (Observable)combineLatest(Arrays.asList(o1, o2, o3, o4, o5, o6), Functions.fromFunc(combineFunction)); } /** @@ -773,7 +773,7 @@ public static Observable combineLatest(Observable @SuppressWarnings("unchecked") public static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Func7 combineFunction) { - return combineLatest(Arrays.asList(o1, o2, o3, o4, o5, o6, o7), Functions.fromFunc(combineFunction)); + return (Observable)combineLatest(Arrays.asList(o1, o2, o3, o4, o5, o6, o7), Functions.fromFunc(combineFunction)); } /** @@ -812,7 +812,7 @@ public static Observable combineLatest(Observ @SuppressWarnings("unchecked") public static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, Func8 combineFunction) { - return combineLatest(Arrays.asList(o1, o2, o3, o4, o5, o6, o7, o8), Functions.fromFunc(combineFunction)); + return (Observable)combineLatest(Arrays.asList(o1, o2, o3, o4, o5, o6, o7, o8), Functions.fromFunc(combineFunction)); } /** @@ -854,7 +854,7 @@ public static Observable combineLatest(Ob public static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, Observable o9, Func9 combineFunction) { - return combineLatest(Arrays.asList(o1, o2, o3, o4, o5, o6, o7, o8, o9), Functions.fromFunc(combineFunction)); + return (Observable)combineLatest(Arrays.asList(o1, o2, o3, o4, o5, o6, o7, o8, o9), Functions.fromFunc(combineFunction)); } /** * Combines a list of source Observables by emitting an item that aggregates the latest values of each of @@ -1330,7 +1330,7 @@ public static Observable error(Throwable exception) { * @see ReactiveX operators documentation: From */ public static Observable from(Future future) { - return create(OnSubscribeToObservableFuture.toObservableFuture(future)); + return (Observable)create(OnSubscribeToObservableFuture.toObservableFuture(future)); } /** @@ -1361,7 +1361,7 @@ public static Observable from(Future future) { * @see ReactiveX operators documentation: From */ public static Observable from(Future future, long timeout, TimeUnit unit) { - return create(OnSubscribeToObservableFuture.toObservableFuture(future, timeout, unit)); + return (Observable)create(OnSubscribeToObservableFuture.toObservableFuture(future, timeout, unit)); } /** @@ -1390,7 +1390,8 @@ public static Observable from(Future future, long timeout, T */ public static Observable from(Future future, Scheduler scheduler) { // TODO in a future revision the Scheduler will become important because we'll start polling instead of blocking on the Future - return create(OnSubscribeToObservableFuture.toObservableFuture(future)).subscribeOn(scheduler); + Observable o = (Observable)create(OnSubscribeToObservableFuture.toObservableFuture(future)); + return o.subscribeOn(scheduler); } /** @@ -5834,7 +5835,7 @@ public final Observable flatMapIterable(Func1 Observable flatMapIterable(Func1> collectionSelector, Func2 resultSelector) { - return flatMap(OperatorMapPair.convertSelector(collectionSelector), resultSelector); + return (Observable)flatMap(OperatorMapPair.convertSelector(collectionSelector), resultSelector); } /** @@ -5870,7 +5871,7 @@ public final Observable flatMapIterable(Func1 Observable flatMapIterable(Func1> collectionSelector, Func2 resultSelector, int maxConcurrent) { - return flatMap(OperatorMapPair.convertSelector(collectionSelector), resultSelector, maxConcurrent); + return (Observable)flatMap(OperatorMapPair.convertSelector(collectionSelector), resultSelector, maxConcurrent); } /** @@ -6655,7 +6656,7 @@ public final Observable onErrorResumeNext(final Func1ReactiveX operators documentation: Catch */ public final Observable onErrorResumeNext(final Observable resumeSequence) { - return lift(OperatorOnErrorResumeNextViaFunction.withOther(resumeSequence)); + return lift((Operator)OperatorOnErrorResumeNextViaFunction.withOther(resumeSequence)); } /** @@ -6685,7 +6686,7 @@ public final Observable onErrorResumeNext(final Observable resum * @see ReactiveX operators documentation: Catch */ public final Observable onErrorReturn(Func1 resumeFunction) { - return lift(OperatorOnErrorResumeNextViaFunction.withSingle(resumeFunction)); + return lift((Operator)OperatorOnErrorResumeNextViaFunction.withSingle(resumeFunction)); } /** @@ -6721,7 +6722,7 @@ public final Observable onErrorReturn(Func1 resumeFun * @see ReactiveX operators documentation: Catch */ public final Observable onExceptionResumeNext(final Observable resumeSequence) { - return lift(OperatorOnErrorResumeNextViaFunction.withException(resumeSequence)); + return lift((Operator)OperatorOnErrorResumeNextViaFunction.withException(resumeSequence)); } /** @@ -10564,7 +10565,7 @@ public final Observable zipWith(Iterable other, Func2ReactiveX operators documentation: Zip */ public final Observable zipWith(Observable other, Func2 zipFunction) { - return zip(this, other, zipFunction); + return (Observable)zip(this, other, zipFunction); } /** diff --git a/src/main/java/rx/Single.java b/src/main/java/rx/Single.java index 57dadd12d8..c1565313ab 100644 --- a/src/main/java/rx/Single.java +++ b/src/main/java/rx/Single.java @@ -524,7 +524,7 @@ public void call(SingleSubscriber te) { * @see ReactiveX operators documentation: From */ public static Single from(Future future) { - return new Single(OnSubscribeToObservableFuture.toObservableFuture(future)); + return new Single((Observable.OnSubscribe)OnSubscribeToObservableFuture.toObservableFuture(future)); } /** @@ -555,7 +555,7 @@ public static Single from(Future future) { * @see ReactiveX operators documentation: From */ public static Single from(Future future, long timeout, TimeUnit unit) { - return new Single(OnSubscribeToObservableFuture.toObservableFuture(future, timeout, unit)); + return new Single((Observable.OnSubscribe)OnSubscribeToObservableFuture.toObservableFuture(future, timeout, unit)); } /** @@ -583,7 +583,7 @@ public static Single from(Future future, long timeout, TimeU * @see ReactiveX operators documentation: From */ public static Single from(Future future, Scheduler scheduler) { - return new Single(OnSubscribeToObservableFuture.toObservableFuture(future)).subscribeOn(scheduler); + return new Single((Observable.OnSubscribe)OnSubscribeToObservableFuture.toObservableFuture(future)).subscribeOn(scheduler); } /** @@ -949,7 +949,7 @@ public static Observable merge(Single t1, SingleReactiveX operators documentation: Zip */ public static Single zip(Single s1, Single s2, final Func2 zipFunction) { - return SingleOperatorZip.zip(new Single[] {s1, s2}, new FuncN() { + return SingleOperatorZip.zip(new Single[] {s1, s2}, new FuncN() { @Override public R call(Object... args) { return zipFunction.call((T1) args[0], (T2) args[1]); @@ -980,7 +980,7 @@ public R call(Object... args) { * @see ReactiveX operators documentation: Zip */ public static Single zip(Single s1, Single s2, Single s3, final Func3 zipFunction) { - return SingleOperatorZip.zip(new Single[] {s1, s2, s3}, new FuncN() { + return SingleOperatorZip.zip(new Single[] {s1, s2, s3}, new FuncN() { @Override public R call(Object... args) { return zipFunction.call((T1) args[0], (T2) args[1], (T3) args[2]); @@ -1013,7 +1013,7 @@ public R call(Object... args) { * @see ReactiveX operators documentation: Zip */ public static Single zip(Single s1, Single s2, Single s3, Single s4, final Func4 zipFunction) { - return SingleOperatorZip.zip(new Single[] {s1, s2, s3, s4}, new FuncN() { + return SingleOperatorZip.zip(new Single[] {s1, s2, s3, s4}, new FuncN() { @Override public R call(Object... args) { return zipFunction.call((T1) args[0], (T2) args[1], (T3) args[2], (T4) args[3]); @@ -1048,7 +1048,7 @@ public R call(Object... args) { * @see ReactiveX operators documentation: Zip */ public static Single zip(Single s1, Single s2, Single s3, Single s4, Single s5, final Func5 zipFunction) { - return SingleOperatorZip.zip(new Single[] {s1, s2, s3, s4, s5}, new FuncN() { + return SingleOperatorZip.zip(new Single[] {s1, s2, s3, s4, s5}, new FuncN() { @Override public R call(Object... args) { return zipFunction.call((T1) args[0], (T2) args[1], (T3) args[2], (T4) args[3], (T5) args[4]); @@ -1086,7 +1086,7 @@ public R call(Object... args) { */ public static Single zip(Single s1, Single s2, Single s3, Single s4, Single s5, Single s6, final Func6 zipFunction) { - return SingleOperatorZip.zip(new Single[] {s1, s2, s3, s4, s5, s6}, new FuncN() { + return SingleOperatorZip.zip(new Single[] {s1, s2, s3, s4, s5, s6}, new FuncN() { @Override public R call(Object... args) { return zipFunction.call((T1) args[0], (T2) args[1], (T3) args[2], (T4) args[3], (T5) args[4], (T6) args[5]); @@ -1126,7 +1126,7 @@ public R call(Object... args) { */ public static Single zip(Single s1, Single s2, Single s3, Single s4, Single s5, Single s6, Single s7, final Func7 zipFunction) { - return SingleOperatorZip.zip(new Single[] {s1, s2, s3, s4, s5, s6, s7}, new FuncN() { + return SingleOperatorZip.zip(new Single[] {s1, s2, s3, s4, s5, s6, s7}, new FuncN() { @Override public R call(Object... args) { return zipFunction.call((T1) args[0], (T2) args[1], (T3) args[2], (T4) args[3], (T5) args[4], (T6) args[5], (T7) args[6]); @@ -1168,7 +1168,7 @@ public R call(Object... args) { */ public static Single zip(Single s1, Single s2, Single s3, Single s4, Single s5, Single s6, Single s7, Single s8, final Func8 zipFunction) { - return SingleOperatorZip.zip(new Single[] {s1, s2, s3, s4, s5, s6, s7, s8}, new FuncN() { + return SingleOperatorZip.zip(new Single[] {s1, s2, s3, s4, s5, s6, s7, s8}, new FuncN() { @Override public R call(Object... args) { return zipFunction.call((T1) args[0], (T2) args[1], (T3) args[2], (T4) args[3], (T5) args[4], (T6) args[5], (T7) args[6], (T8) args[7]); @@ -1212,7 +1212,7 @@ public R call(Object... args) { */ public static Single zip(Single s1, Single s2, Single s3, Single s4, Single s5, Single s6, Single s7, Single s8, Single s9, final Func9 zipFunction) { - return SingleOperatorZip.zip(new Single[] {s1, s2, s3, s4, s5, s6, s7, s8, s9}, new FuncN() { + return SingleOperatorZip.zip(new Single[] {s1, s2, s3, s4, s5, s6, s7, s8, s9}, new FuncN() { @Override public R call(Object... args) { return zipFunction.call((T1) args[0], (T2) args[1], (T3) args[2], (T4) args[3], (T5) args[4], (T6) args[5], (T7) args[6], (T8) args[7], (T9) args[8]); @@ -1242,7 +1242,8 @@ public R call(Object... args) { * @see ReactiveX operators documentation: Zip */ public static Single zip(Iterable> singles, FuncN zipFunction) { - return SingleOperatorZip.zip(iterableToArray(singles), zipFunction); + Single[] iterableToArray = iterableToArray(singles); + return SingleOperatorZip.zip(iterableToArray, zipFunction); } /** @@ -1401,7 +1402,7 @@ public final Single observeOn(Scheduler scheduler) { * @see ReactiveX operators documentation: Catch */ public final Single onErrorReturn(Func1 resumeFunction) { - return lift(OperatorOnErrorResumeNextViaFunction.withSingle(resumeFunction)); + return lift((Operator)OperatorOnErrorResumeNextViaFunction.withSingle(resumeFunction)); } /** @@ -2234,7 +2235,7 @@ public final BlockingSingle toBlocking() { * @see ReactiveX operators documentation: Zip */ public final Single zipWith(Single other, Func2 zipFunction) { - return zip(this, other, zipFunction); + return (Single)zip(this, other, zipFunction); } /** diff --git a/src/main/java/rx/internal/operators/BlockingOperatorLatest.java b/src/main/java/rx/internal/operators/BlockingOperatorLatest.java index 5b2b798995..b53ab211a7 100644 --- a/src/main/java/rx/internal/operators/BlockingOperatorLatest.java +++ b/src/main/java/rx/internal/operators/BlockingOperatorLatest.java @@ -49,7 +49,7 @@ public static Iterable latest(final Observable source) { @Override public Iterator iterator() { LatestObserverIterator lio = new LatestObserverIterator(); - source.materialize().subscribe(lio); + ((Observable)source).materialize().subscribe(lio); return lio; } }; diff --git a/src/main/java/rx/internal/operators/BlockingOperatorMostRecent.java b/src/main/java/rx/internal/operators/BlockingOperatorMostRecent.java index 9b6cf64bf2..4cd424be29 100644 --- a/src/main/java/rx/internal/operators/BlockingOperatorMostRecent.java +++ b/src/main/java/rx/internal/operators/BlockingOperatorMostRecent.java @@ -53,7 +53,7 @@ public Iterator iterator() { * Subscribe instead of unsafeSubscribe since this is the final subscribe in the chain * since it is for BlockingObservable. */ - source.subscribe(mostRecentObserver); + ((Observable)source).subscribe(mostRecentObserver); return mostRecentObserver.getIterable(); } diff --git a/src/main/java/rx/internal/operators/BlockingOperatorNext.java b/src/main/java/rx/internal/operators/BlockingOperatorNext.java index 7a55a663eb..fc6a41487b 100644 --- a/src/main/java/rx/internal/operators/BlockingOperatorNext.java +++ b/src/main/java/rx/internal/operators/BlockingOperatorNext.java @@ -96,7 +96,7 @@ private boolean moveToNext() { started = true; // if not started, start now observer.setWaiting(1); - items.materialize().subscribe(observer); + ((Observable)items).materialize().subscribe(observer); } Notification nextNotification = observer.takeNext(); diff --git a/src/main/java/rx/internal/operators/BlockingOperatorToFuture.java b/src/main/java/rx/internal/operators/BlockingOperatorToFuture.java index 29021405ca..daeee2e770 100644 --- a/src/main/java/rx/internal/operators/BlockingOperatorToFuture.java +++ b/src/main/java/rx/internal/operators/BlockingOperatorToFuture.java @@ -54,7 +54,7 @@ public static Future toFuture(Observable that) { final AtomicReference value = new AtomicReference(); final AtomicReference error = new AtomicReference(); - final Subscription s = that.single().subscribe(new Subscriber() { + final Subscription s = ((Observable)that).single().subscribe(new Subscriber() { @Override public void onCompleted() { diff --git a/src/main/java/rx/internal/operators/BlockingOperatorToIterator.java b/src/main/java/rx/internal/operators/BlockingOperatorToIterator.java index 899aaffacb..6f0f9b2616 100644 --- a/src/main/java/rx/internal/operators/BlockingOperatorToIterator.java +++ b/src/main/java/rx/internal/operators/BlockingOperatorToIterator.java @@ -50,7 +50,7 @@ public static Iterator toIterator(Observable source) { SubscriberIterator subscriber = new SubscriberIterator(); // using subscribe instead of unsafeSubscribe since this is a BlockingObservable "final subscribe" - source.materialize().subscribe(subscriber); + ((Observable)source).materialize().subscribe(subscriber); return subscriber; } diff --git a/src/main/java/rx/internal/operators/CachedObservable.java b/src/main/java/rx/internal/operators/CachedObservable.java index 1995174eff..6d9e7b6da6 100644 --- a/src/main/java/rx/internal/operators/CachedObservable.java +++ b/src/main/java/rx/internal/operators/CachedObservable.java @@ -39,7 +39,7 @@ public final class CachedObservable extends Observable { * @return the CachedObservable instance */ public static CachedObservable from(Observable source) { - return from(source, 16); + return (CachedObservable)from(source, 16); } /** diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcat.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcat.java index c7da20df07..8257c158a7 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcat.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcat.java @@ -26,11 +26,11 @@ import rx.subscriptions.SerialSubscription; public final class CompletableOnSubscribeConcat implements CompletableOnSubscribe { - final Observable sources; + final Observable sources; final int prefetch; public CompletableOnSubscribeConcat(Observable sources, int prefetch) { - this.sources = sources; + this.sources = (Observable)sources; this.prefetch = prefetch; } diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeMerge.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeMerge.java index a1c3cf64e9..7c3fff300c 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeMerge.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeMerge.java @@ -28,12 +28,12 @@ import rx.subscriptions.CompositeSubscription; public final class CompletableOnSubscribeMerge implements CompletableOnSubscribe { - final Observable source; + final Observable source; final int maxConcurrency; final boolean delayErrors; public CompletableOnSubscribeMerge(Observable source, int maxConcurrency, boolean delayErrors) { - this.source = source; + this.source = (Observable)source; this.maxConcurrency = maxConcurrency; this.delayErrors = delayErrors; } diff --git a/src/main/java/rx/internal/operators/OnSubscribeCombineLatest.java b/src/main/java/rx/internal/operators/OnSubscribeCombineLatest.java index 93dcb5de5d..f254331913 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeCombineLatest.java +++ b/src/main/java/rx/internal/operators/OnSubscribeCombineLatest.java @@ -140,7 +140,7 @@ public void subscribe(Observable[] sources) { if (cancelled) { return; } - sources[i].subscribe(as[i]); + ((Observable)sources[i]).subscribe(as[i]); } } diff --git a/src/main/java/rx/internal/operators/OperatorMapPair.java b/src/main/java/rx/internal/operators/OperatorMapPair.java index 29848d2f78..b16a9b3c41 100644 --- a/src/main/java/rx/internal/operators/OperatorMapPair.java +++ b/src/main/java/rx/internal/operators/OperatorMapPair.java @@ -47,7 +47,7 @@ public static Func1> convertSelector(final Func1>() { @Override public Observable call(T t1) { - return Observable.from(selector.call(t1)); + return (Observable)Observable.from(selector.call(t1)); } }; } diff --git a/src/main/java/rx/internal/operators/OperatorMulticast.java b/src/main/java/rx/internal/operators/OperatorMulticast.java index fcdededbed..6b760161da 100644 --- a/src/main/java/rx/internal/operators/OperatorMulticast.java +++ b/src/main/java/rx/internal/operators/OperatorMulticast.java @@ -149,6 +149,6 @@ public void onCompleted() { sub = subscription; } if (sub != null) - source.subscribe(sub); + ((Observable)source).subscribe(sub); } } \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OperatorReplay.java b/src/main/java/rx/internal/operators/OperatorReplay.java index f4af56bcb9..4ca4bda3e9 100644 --- a/src/main/java/rx/internal/operators/OperatorReplay.java +++ b/src/main/java/rx/internal/operators/OperatorReplay.java @@ -135,7 +135,7 @@ public static ConnectableObservable create(Observable source public static ConnectableObservable create(Observable source, final int bufferSize) { if (bufferSize == Integer.MAX_VALUE) { - return create(source); + return (ConnectableObservable)create(source); } return create(source, new Func0>() { @Override @@ -155,7 +155,7 @@ public ReplayBuffer call() { */ public static ConnectableObservable create(Observable source, long maxAge, TimeUnit unit, Scheduler scheduler) { - return create(source, maxAge, unit, scheduler, Integer.MAX_VALUE); + return (ConnectableObservable)create(source, maxAge, unit, scheduler, Integer.MAX_VALUE); } /** diff --git a/src/main/java/rx/internal/operators/SingleOnSubscribeDelaySubscriptionOther.java b/src/main/java/rx/internal/operators/SingleOnSubscribeDelaySubscriptionOther.java index efd9cf517b..a2d47dfee7 100644 --- a/src/main/java/rx/internal/operators/SingleOnSubscribeDelaySubscriptionOther.java +++ b/src/main/java/rx/internal/operators/SingleOnSubscribeDelaySubscriptionOther.java @@ -86,6 +86,6 @@ public void onCompleted() { serial.set(otherSubscriber); - other.subscribe(otherSubscriber); + ((Observable)other).subscribe(otherSubscriber); } } diff --git a/src/main/java/rx/observables/AsyncOnSubscribe.java b/src/main/java/rx/observables/AsyncOnSubscribe.java index 24de19c149..c0e5a166b8 100644 --- a/src/main/java/rx/observables/AsyncOnSubscribe.java +++ b/src/main/java/rx/observables/AsyncOnSubscribe.java @@ -608,12 +608,13 @@ public void onCompleted() { }; subscriptions.add(s); - t.doOnTerminate(new Action0() { + Observable doOnTerminate = t.doOnTerminate(new Action0() { @Override public void call() { subscriptions.remove(s); - }}) - .subscribe(s); + }}); + + ((Observable)doOnTerminate).subscribe(s); merger.onNext(buffer); } diff --git a/src/main/java/rx/observables/BlockingObservable.java b/src/main/java/rx/observables/BlockingObservable.java index a44bf85558..33bde77d6a 100644 --- a/src/main/java/rx/observables/BlockingObservable.java +++ b/src/main/java/rx/observables/BlockingObservable.java @@ -99,7 +99,7 @@ public void forEach(final Action1 onNext) { * Use 'subscribe' instead of 'unsafeSubscribe' for Rx contract behavior * (see http://reactivex.io/documentation/contract.html) as this is the final subscribe in the chain. */ - Subscription subscription = o.subscribe(new Subscriber() { + Subscription subscription = ((Observable)o).subscribe(new Subscriber() { @Override public void onCompleted() { latch.countDown(); @@ -144,7 +144,7 @@ public void onNext(T args) { * @see ReactiveX documentation: To */ public Iterator getIterator() { - return BlockingOperatorToIterator.toIterator(o); + return BlockingOperatorToIterator.toIterator((Observable)o); } /** @@ -299,7 +299,7 @@ public Iterable mostRecent(T initialValue) { * @see ReactiveX documentation: TakeLast */ public Iterable next() { - return BlockingOperatorNext.next(o); + return BlockingOperatorNext.next((Observable)o); } /** @@ -316,7 +316,7 @@ public Iterable next() { * @see ReactiveX documentation: First */ public Iterable latest() { - return BlockingOperatorLatest.latest(o); + return BlockingOperatorLatest.latest((Observable)o); } /** @@ -398,7 +398,7 @@ public T singleOrDefault(T defaultValue, Func1 predicate) { * @see ReactiveX documentation: To */ public Future toFuture() { - return BlockingOperatorToFuture.toFuture(o); + return BlockingOperatorToFuture.toFuture((Observable)o); } /** @@ -430,7 +430,7 @@ private T blockForSingle(final Observable observable) { final AtomicReference returnException = new AtomicReference(); final CountDownLatch latch = new CountDownLatch(1); - Subscription subscription = observable.subscribe(new Subscriber() { + Subscription subscription = ((Observable)observable).subscribe(new Subscriber() { @Override public void onCompleted() { latch.countDown(); @@ -467,7 +467,7 @@ public void onNext(final T item) { public void subscribe() { final CountDownLatch cdl = new CountDownLatch(1); final Throwable[] error = { null }; - Subscription s = o.subscribe(new Subscriber() { + Subscription s = ((Observable)o).subscribe(new Subscriber() { @Override public void onNext(T t) { @@ -504,7 +504,7 @@ public void subscribe(Observer observer) { final NotificationLite nl = NotificationLite.instance(); final BlockingQueue queue = new LinkedBlockingQueue(); - Subscription s = o.subscribe(new Subscriber() { + Subscription s = ((Observable)o).subscribe(new Subscriber() { @Override public void onNext(T t) { queue.offer(nl.next(t)); @@ -592,7 +592,7 @@ public void call() { } })); - o.subscribe(s); + ((Observable)o).subscribe(s); try { for (;;) { diff --git a/src/main/java/rx/singles/BlockingSingle.java b/src/main/java/rx/singles/BlockingSingle.java index 6821bc5b82..9d327f6b6a 100644 --- a/src/main/java/rx/singles/BlockingSingle.java +++ b/src/main/java/rx/singles/BlockingSingle.java @@ -100,7 +100,7 @@ public void onError(Throwable error) { */ @Experimental public Future toFuture() { - return BlockingOperatorToFuture.toFuture(single.toObservable()); + return BlockingOperatorToFuture.toFuture(((Single)single).toObservable()); } } From c8e1b039ca6e6fddf5558657645043448d75ee77 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Mon, 4 Apr 2016 20:39:52 +0200 Subject: [PATCH 011/294] 1.x: make defensive copy of the properties in RxJavaPlugins --- src/main/java/rx/plugins/RxJavaPlugins.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/rx/plugins/RxJavaPlugins.java b/src/main/java/rx/plugins/RxJavaPlugins.java index 6391a91185..acedd07449 100644 --- a/src/main/java/rx/plugins/RxJavaPlugins.java +++ b/src/main/java/rx/plugins/RxJavaPlugins.java @@ -200,7 +200,11 @@ public void registerSingleExecutionHook(RxJavaSingleExecutionHook impl) { } } - /* test */ static Object getPluginImplementationViaProperty(Class pluginClass, Properties props) { + /* test */ static Object getPluginImplementationViaProperty(Class pluginClass, Properties propsIn) { + // Make a defensive clone because traversal may fail with ConcurrentModificationException + // if the properties get changed by something outside RxJava. + Properties props = (Properties)propsIn.clone(); + final String classSimpleName = pluginClass.getSimpleName(); /* * Check system properties for plugin class. From 6aee6f884bf8d3ad92fcae4158c5667dd5e405d8 Mon Sep 17 00:00:00 2001 From: adi1133 Date: Thu, 7 Apr 2016 09:56:37 +0300 Subject: [PATCH 012/294] 1.x: Add maxConcurrent parameter to concatMapEager * Add maxConcurrent parameter to concatMapEager * Improve ConcatMapEager performance and add unit test * Add test case and cleanup whitespace --- src/main/java/rx/Observable.java | 36 ++++++++++++++++++- .../operators/OperatorEagerConcatMap.java | 10 ++++-- .../operators/OperatorEagerConcatMapTest.java | 36 ++++++++++++++++--- 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/main/java/rx/Observable.java b/src/main/java/rx/Observable.java index 439da019ed..0b2baab702 100644 --- a/src/main/java/rx/Observable.java +++ b/src/main/java/rx/Observable.java @@ -5353,7 +5353,41 @@ public final Observable concatMapEager(Func1 0 required but it was " + capacityHint); } - return lift(new OperatorEagerConcatMap(mapper, capacityHint)); + return lift(new OperatorEagerConcatMap(mapper, capacityHint, Integer.MAX_VALUE)); + } + + /** + * Maps a sequence of values into Observables and concatenates these Observables eagerly into a single + * Observable. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source Observables. The operator buffers the values emitted by these Observables and then drains them in + * order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream, however, due to the eagerness requirement, sources + * are subscribed to in unbounded mode and their values are queued up in an unbounded buffer.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param mapper the function that maps a sequence of values into a sequence of Observables that will be + * eagerly concatenated + * @param capacityHint hints about the number of expected source sequence values + * @param maxConcurrent the maximum number of concurrent subscribed observables + * @return + * @warn javadoc fails to describe the return value + * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + */ + @Experimental + public final Observable concatMapEager(Func1> mapper, int capacityHint, int maxConcurrent) { + if (capacityHint < 1) { + throw new IllegalArgumentException("capacityHint > 0 required but it was " + capacityHint); + } + if (maxConcurrent < 1) { + throw new IllegalArgumentException("maxConcurrent > 0 required but it was " + capacityHint); + } + return lift(new OperatorEagerConcatMap(mapper, capacityHint, maxConcurrent)); } /** diff --git a/src/main/java/rx/internal/operators/OperatorEagerConcatMap.java b/src/main/java/rx/internal/operators/OperatorEagerConcatMap.java index bbf2bcc48b..cfa46837b5 100644 --- a/src/main/java/rx/internal/operators/OperatorEagerConcatMap.java +++ b/src/main/java/rx/internal/operators/OperatorEagerConcatMap.java @@ -31,14 +31,16 @@ public final class OperatorEagerConcatMap implements Operator { final Func1> mapper; final int bufferSize; - public OperatorEagerConcatMap(Func1> mapper, int bufferSize) { + private final int maxConcurrent; + public OperatorEagerConcatMap(Func1> mapper, int bufferSize, int maxConcurrent) { this.mapper = mapper; this.bufferSize = bufferSize; + this.maxConcurrent = maxConcurrent; } @Override public Subscriber call(Subscriber t) { - EagerOuterSubscriber outer = new EagerOuterSubscriber(mapper, bufferSize, t); + EagerOuterSubscriber outer = new EagerOuterSubscriber(mapper, bufferSize, maxConcurrent, t); outer.init(); return outer; } @@ -82,12 +84,13 @@ static final class EagerOuterSubscriber extends Subscriber { private EagerOuterProducer sharedProducer; public EagerOuterSubscriber(Func1> mapper, int bufferSize, - Subscriber actual) { + int maxConcurrent, Subscriber actual) { this.mapper = mapper; this.bufferSize = bufferSize; this.actual = actual; this.subscribers = new LinkedList>(); this.wip = new AtomicInteger(); + request(maxConcurrent == Integer.MAX_VALUE ? Long.MAX_VALUE : maxConcurrent); } void init() { @@ -223,6 +226,7 @@ void drain() { } innerSubscriber.unsubscribe(); innerDone = true; + request(1); break; } } diff --git a/src/test/java/rx/internal/operators/OperatorEagerConcatMapTest.java b/src/test/java/rx/internal/operators/OperatorEagerConcatMapTest.java index 8d2d40bed4..e6298230e4 100644 --- a/src/test/java/rx/internal/operators/OperatorEagerConcatMapTest.java +++ b/src/test/java/rx/internal/operators/OperatorEagerConcatMapTest.java @@ -16,6 +16,8 @@ package rx.internal.operators; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.*; import org.junit.*; @@ -302,11 +304,16 @@ public Observable call(Integer t) { ts.assertNotCompleted(); ts.assertError(TestException.class); } - + @Test(expected = IllegalArgumentException.class) public void testInvalidCapacityHint() { Observable.just(1).concatMapEager(toJust, 0); } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidMaxConcurrent() { + Observable.just(1).concatMapEager(toJust, RxRingBuffer.SIZE, 0); + } @Test public void testBackpressure() { @@ -397,17 +404,38 @@ public void call(Integer t) { @Test public void testInnerNull() { - TestSubscriber ts = TestSubscriber.create(); - Observable.just(1).concatMapEager(new Func1>() { @Override public Observable call(Integer t) { return Observable.just(null); } }).subscribe(ts); - + ts.assertNoErrors(); ts.assertCompleted(); ts.assertValue(null); } + + + @Test + public void testMaxConcurrent5() { + final List requests = new ArrayList(); + Observable.range(1, 100).doOnRequest(new Action1() { + @Override + public void call(Long reqCount) { + requests.add(reqCount); + } + }).concatMapEager(toJust, RxRingBuffer.SIZE, 5).subscribe(ts); + + ts.assertNoErrors(); + ts.assertValueCount(100); + ts.assertCompleted(); + + Assert.assertEquals(5, (long) requests.get(0)); + Assert.assertEquals(1, (long) requests.get(1)); + Assert.assertEquals(1, (long) requests.get(2)); + Assert.assertEquals(1, (long) requests.get(3)); + Assert.assertEquals(1, (long) requests.get(4)); + Assert.assertEquals(1, (long) requests.get(5)); + } } From 7593d8c4eb5c457ef7afe4ad0162093ec8e22d83 Mon Sep 17 00:00:00 2001 From: Artem Zinnatullin Date: Thu, 7 Apr 2016 09:57:14 +0300 Subject: [PATCH 013/294] 1.x fromCallable() @Experimental -> @Beta --- src/main/java/rx/Observable.java | 2 +- src/main/java/rx/Single.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/rx/Observable.java b/src/main/java/rx/Observable.java index 0b2baab702..bd96ee4556 100644 --- a/src/main/java/rx/Observable.java +++ b/src/main/java/rx/Observable.java @@ -1464,7 +1464,7 @@ public static Observable from(T[] array) { * @see #defer(Func0) * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) */ - @Experimental + @Beta public static Observable fromCallable(Callable func) { return create(new OnSubscribeFromCallable(func)); } diff --git a/src/main/java/rx/Single.java b/src/main/java/rx/Single.java index c1565313ab..b1e2ed4074 100644 --- a/src/main/java/rx/Single.java +++ b/src/main/java/rx/Single.java @@ -603,7 +603,7 @@ public static Single from(Future future, Scheduler scheduler * the type of the item emitted by the {@link Single}. * @return a {@link Single} whose {@link Observer}s' subscriptions trigger an invocation of the given function. */ - @Experimental + @Beta public static Single fromCallable(final Callable func) { return create(new OnSubscribe() { @Override From bcd7fa1ebb73594c69e85c6060fef27e74006e79 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Thu, 7 Apr 2016 08:59:56 +0200 Subject: [PATCH 014/294] 1.x: fix switchMap/switchOnNext producer retention and backpressure --- .../rx/internal/operators/OperatorSwitch.java | 382 ++++++++++-------- .../util/atomic/SpscLinkedArrayQueue.java | 31 +- .../operators/OperatorSwitchTest.java | 91 ++++- 3 files changed, 325 insertions(+), 179 deletions(-) diff --git a/src/main/java/rx/internal/operators/OperatorSwitch.java b/src/main/java/rx/internal/operators/OperatorSwitch.java index 7d706f2a95..bbbcd9879d 100644 --- a/src/main/java/rx/internal/operators/OperatorSwitch.java +++ b/src/main/java/rx/internal/operators/OperatorSwitch.java @@ -16,14 +16,17 @@ package rx.internal.operators; import java.util.*; +import java.util.concurrent.atomic.AtomicLong; import rx.*; import rx.Observable; import rx.Observable.Operator; import rx.exceptions.CompositeException; -import rx.internal.producers.ProducerArbiter; +import rx.functions.Action0; +import rx.internal.util.RxRingBuffer; +import rx.internal.util.atomic.SpscLinkedArrayQueue; import rx.plugins.RxJavaPlugins; -import rx.subscriptions.SerialSubscription; +import rx.subscriptions.*; /** * Transforms an Observable that emits Observables into a single Observable that @@ -45,6 +48,9 @@ private static final class HolderDelayError { static final OperatorSwitch INSTANCE = new OperatorSwitch(true); } /** + * Returns a singleton instance of the operator based on the delayError parameter. + * @param the value type + * @param delayError should the errors of the inner sources delayed until the main sequence completes? * @return a singleton instance of this stateless operator. */ @SuppressWarnings({ "unchecked" }) @@ -72,51 +78,80 @@ public Subscriber> call(final Subscriber extends Subscriber> { final Subscriber child; final SerialSubscription ssub; - final ProducerArbiter arbiter; - final boolean delayError; + final AtomicLong index; + final SpscLinkedArrayQueue queue; + final NotificationLite nl; + + boolean emitting; - long index; + boolean missed; - Throwable error; + long requested; - boolean mainDone; + Producer producer; - List queue; + volatile boolean mainDone; + + Throwable error; boolean innerActive; - boolean emitting; - - boolean missed; + static final Throwable TERMINAL_ERROR = new Throwable("Terminal error"); SwitchSubscriber(Subscriber child, boolean delayError) { this.child = child; - this.arbiter = new ProducerArbiter(); this.ssub = new SerialSubscription(); this.delayError = delayError; + this.index = new AtomicLong(); + this.queue = new SpscLinkedArrayQueue(RxRingBuffer.SIZE); + this.nl = NotificationLite.instance(); } void init() { child.add(ssub); + child.add(Subscriptions.create(new Action0() { + @Override + public void call() { + clearProducer(); + } + })); child.setProducer(new Producer(){ @Override public void request(long n) { - if (n > 0) { - arbiter.request(n); + if (n > 0L) { + childRequested(n); + } else + if (n < 0L) { + throw new IllegalArgumentException("n >= 0 expected but it was " + n); } } }); } - + + void clearProducer() { + synchronized (this) { + producer = null; + } + } + @Override public void onNext(Observable t) { + long id = index.incrementAndGet(); + + Subscription s = ssub.get(); + if (s != null) { + s.unsubscribe(); + } + InnerSubscriber inner; + synchronized (this) { - long id = ++index; inner = new InnerSubscriber(id, this); + innerActive = true; + producer = null; } ssub.set(inner); @@ -125,201 +160,228 @@ public void onNext(Observable t) { @Override public void onError(Throwable e) { + boolean success; + synchronized (this) { - e = updateError(e); + success = updateError(e); + } + if (success) { mainDone = true; - - if (emitting) { - missed = true; - return; - } - if (delayError && innerActive) { - return; - } - emitting = true; + drain(); + } else { + pluginError(e); } - - child.onError(e); } + boolean updateError(Throwable next) { + Throwable e = error; + if (e == TERMINAL_ERROR) { + return false; + } else + if (e == null) { + error = next; + } else + if (e instanceof CompositeException) { + List list = new ArrayList(((CompositeException)e).getExceptions()); + list.add(next); + error = new CompositeException(list); + } else { + error = new CompositeException(e, next); + } + return true; + } + @Override public void onCompleted() { - Throwable ex; + mainDone = true; + drain(); + } + + void emit(T value, InnerSubscriber inner) { synchronized (this) { - mainDone = true; - if (emitting) { - missed = true; + if (index.get() != inner.id) { return; } - if (innerActive) { - return; + + queue.offer(inner, nl.next(value)); + } + drain(); + } + + void error(Throwable e, long id) { + boolean success; + synchronized (this) { + if (index.get() == id) { + success = updateError(e); + innerActive = false; + producer = null; + } else { + success = true; } - emitting = true; - ex = error; } - if (ex == null) { - child.onCompleted(); + if (success) { + drain(); } else { - child.onError(ex); + pluginError(e); } } - Throwable updateError(Throwable e) { - Throwable ex = error; - if (ex == null) { - error = e; - } else - if (ex instanceof CompositeException) { - CompositeException ce = (CompositeException) ex; - List list = new ArrayList(ce.getExceptions()); - list.add(e); - e = new CompositeException(list); - error = e; - } else { - e = new CompositeException(Arrays.asList(ex, e)); - error = e; + void complete(long id) { + synchronized (this) { + if (index.get() != id) { + return; + } + innerActive = false; + producer = null; } - return e; + drain(); + } + + void pluginError(Throwable e) { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); } - void emit(T value, long id) { + void innerProducer(Producer p, long id) { + long n; synchronized (this) { - if (id != index) { + if (index.get() != id) { return; } - + n = requested; + producer = p; + } + + p.request(n); + } + + void childRequested(long n) { + Producer p; + synchronized (this) { + p = producer; + requested = BackpressureUtils.addCap(requested, n); + } + if (p != null) { + p.request(n); + } + drain(); + } + + void drain() { + boolean localMainDone = mainDone; + boolean localInnerActive; + long localRequested; + Throwable localError; + synchronized (this) { if (emitting) { - List q = queue; - if (q == null) { - q = new ArrayList(4); - queue = q; - } - q.add(value); missed = true; return; } - emitting = true; + localInnerActive = innerActive; + localRequested = requested; + localError = error; + if (localError != null && localError != TERMINAL_ERROR && !delayError) { + error = TERMINAL_ERROR; + } } - - child.onNext(value); - - arbiter.produced(1); - + + final SpscLinkedArrayQueue localQueue = queue; + final AtomicLong localIndex = index; + final Subscriber localChild = child; + for (;;) { - if (child.isUnsubscribed()) { - return; - } - - Throwable localError; - boolean localMainDone; - boolean localActive; - List localQueue; - synchronized (this) { - if (!missed) { - emitting = false; + + long localEmission = 0L; + + while (localEmission != localRequested) { + if (localChild.isUnsubscribed()) { return; } + + boolean empty = localQueue.isEmpty(); - localError = error; - localMainDone = mainDone; - localQueue = queue; - localActive = innerActive; - } - - if (!delayError && localError != null) { - child.onError(localError); - return; - } - - if (localQueue == null && !localActive && localMainDone) { - if (localError != null) { - child.onError(localError); - } else { - child.onCompleted(); + if (checkTerminated(localMainDone, localInnerActive, localError, + localQueue, localChild, empty)) { + return; + } + + if (empty) { + break; + } + + @SuppressWarnings("unchecked") + InnerSubscriber inner = (InnerSubscriber)localQueue.poll(); + T value = nl.getValue(localQueue.poll()); + + if (localIndex.get() == inner.id) { + localChild.onNext(value); + localEmission++; } - return; } - if (localQueue != null) { - int n = 0; - for (T v : localQueue) { - if (child.isUnsubscribed()) { - return; - } - - child.onNext(v); - n++; + if (localEmission == localRequested) { + if (localChild.isUnsubscribed()) { + return; } - arbiter.produced(n); + if (checkTerminated(mainDone, localInnerActive, localError, localQueue, + localChild, localQueue.isEmpty())) { + return; + } } - } - } - - void error(Throwable e, long id) { - boolean drop; - synchronized (this) { - if (id == index) { - innerActive = false; - - e = updateError(e); + + + synchronized (this) { - if (emitting) { - missed = true; - return; + localRequested = requested; + if (localRequested != Long.MAX_VALUE) { + localRequested -= localEmission; + requested = localRequested; } - if (delayError && !mainDone) { + + if (!missed) { + emitting = false; return; } - emitting = true; + missed = false; - drop = false; - } else { - drop = true; + localMainDone = mainDone; + localInnerActive = innerActive; + localError = error; + if (localError != null && localError != TERMINAL_ERROR && !delayError) { + error = TERMINAL_ERROR; + } } } - - if (drop) { - pluginError(e); - } else { - child.onError(e); - } } - - void complete(long id) { - Throwable ex; - synchronized (this) { - if (id != index) { - return; - } - innerActive = false; - - if (emitting) { - missed = true; - return; - } - - ex = error; - if (!mainDone) { - return; + protected boolean checkTerminated(boolean localMainDone, boolean localInnerActive, Throwable localError, + final SpscLinkedArrayQueue localQueue, final Subscriber localChild, boolean empty) { + if (delayError) { + if (localMainDone && !localInnerActive && empty) { + if (localError != null) { + localChild.onError(localError); + } else { + localChild.onCompleted(); + } + return true; } - } - - if (ex != null) { - child.onError(ex); } else { - child.onCompleted(); + if (localError != null) { + localQueue.clear(); + localChild.onError(localError); + return true; + } else + if (localMainDone && !localInnerActive && empty) { + localChild.onCompleted(); + return true; + } } - } - - void pluginError(Throwable e) { - RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + return false; } } - private static final class InnerSubscriber extends Subscriber { + static final class InnerSubscriber extends Subscriber { private final long id; @@ -332,12 +394,12 @@ private static final class InnerSubscriber extends Subscriber { @Override public void setProducer(Producer p) { - parent.arbiter.setProducer(p); + parent.innerProducer(p, id); } @Override public void onNext(T t) { - parent.emit(t, id); + parent.emit(t, this); } @Override diff --git a/src/main/java/rx/internal/util/atomic/SpscLinkedArrayQueue.java b/src/main/java/rx/internal/util/atomic/SpscLinkedArrayQueue.java index 33472a40da..23d8ad7c9e 100644 --- a/src/main/java/rx/internal/util/atomic/SpscLinkedArrayQueue.java +++ b/src/main/java/rx/internal/util/atomic/SpscLinkedArrayQueue.java @@ -30,23 +30,19 @@ /** * A single-producer single-consumer array-backed queue which can allocate new arrays in case the consumer is slower * than the producer. + * + * @param the element type, not null */ public final class SpscLinkedArrayQueue implements Queue { static final int MAX_LOOK_AHEAD_STEP = Integer.getInteger("jctools.spsc.max.lookahead.step", 4096); - protected volatile long producerIndex; - @SuppressWarnings("rawtypes") - static final AtomicLongFieldUpdater PRODUCER_INDEX = - AtomicLongFieldUpdater.newUpdater(SpscLinkedArrayQueue.class, "producerIndex"); + protected final AtomicLong producerIndex; protected int producerLookAheadStep; protected long producerLookAhead; protected int producerMask; protected AtomicReferenceArray producerBuffer; protected int consumerMask; protected AtomicReferenceArray consumerBuffer; - protected volatile long consumerIndex; - @SuppressWarnings("rawtypes") - static final AtomicLongFieldUpdater CONSUMER_INDEX = - AtomicLongFieldUpdater.newUpdater(SpscLinkedArrayQueue.class, "consumerIndex"); + protected final AtomicLong consumerIndex; private static final Object HAS_NEXT = new Object(); public SpscLinkedArrayQueue(final int bufferSize) { @@ -59,7 +55,8 @@ public SpscLinkedArrayQueue(final int bufferSize) { consumerBuffer = buffer; consumerMask = mask; producerLookAhead = mask - 1; // we know it's all empty to start with - soProducerIndex(0L); + producerIndex = new AtomicLong(); + consumerIndex = new AtomicLong(); } /** @@ -219,27 +216,27 @@ private void adjustLookAheadStep(int capacity) { } private long lvProducerIndex() { - return producerIndex; + return producerIndex.get(); } private long lvConsumerIndex() { - return consumerIndex; + return consumerIndex.get(); } private long lpProducerIndex() { - return producerIndex; + return producerIndex.get(); } private long lpConsumerIndex() { - return consumerIndex; + return consumerIndex.get(); } private void soProducerIndex(long v) { - PRODUCER_INDEX.lazySet(this, v); + producerIndex.lazySet(v); } private void soConsumerIndex(long v) { - CONSUMER_INDEX.lazySet(this, v); + consumerIndex.lazySet(v); } private static int calcWrappedOffset(long index, int mask) { @@ -321,11 +318,11 @@ public T element() { *

Don't use the regular offer() with this at all! * @param first * @param second - * @return + * @return always true */ public boolean offer(T first, T second) { final AtomicReferenceArray buffer = producerBuffer; - final long p = producerIndex; + final long p = lvProducerIndex(); final int m = producerMask; int pi = calcWrappedOffset(p + 2, m); diff --git a/src/test/java/rx/internal/operators/OperatorSwitchTest.java b/src/test/java/rx/internal/operators/OperatorSwitchTest.java index 55170ab9ff..b673b56949 100644 --- a/src/test/java/rx/internal/operators/OperatorSwitchTest.java +++ b/src/test/java/rx/internal/operators/OperatorSwitchTest.java @@ -19,6 +19,7 @@ import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; +import java.lang.ref.WeakReference; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -32,7 +33,7 @@ import rx.exceptions.*; import rx.functions.*; import rx.observers.TestSubscriber; -import rx.schedulers.TestScheduler; +import rx.schedulers.*; import rx.subjects.PublishSubject; public class OperatorSwitchTest { @@ -654,7 +655,7 @@ public Observable call(Long t) { ts.requestMore(Long.MAX_VALUE - 1); ts.awaitTerminalEvent(); assertTrue(ts.getOnNextEvents().size() > 0); - assertEquals(5, requests.size()); + assertEquals(4, requests.size()); // depends on the request pattern assertEquals(Long.MAX_VALUE, (long) requests.get(requests.size()-1)); } @@ -790,4 +791,90 @@ public Observable call(Integer v) { ts.assertCompleted(); } + Object ref; + + @Test + public void producerIsNotRetained() throws Exception { + ref = new Object(); + + WeakReference wr = new WeakReference(ref); + + PublishSubject> ps = PublishSubject.create(); + + Subscriber observer = new Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onNext(Object t) { + } + }; + + Observable.switchOnNext(ps).subscribe(observer); + + ps.onNext(Observable.just(ref)); + + ref = null; + + System.gc(); + + Thread.sleep(500); + + Assert.assertNotNull(observer); // retain every other referenec in the pipeline + Assert.assertNotNull(ps); + Assert.assertNull("Object retained!", wr.get()); + } + + @Test + public void switchAsyncHeavily() { + for (int i = 1; i < 1024; i *= 2) { + System.out.println("switchAsyncHeavily >> " + i); + + final Queue q = new ConcurrentLinkedQueue(); + + final int j = i; + TestSubscriber ts = new TestSubscriber(i) { + int count; + @Override + public void onNext(Integer t) { + super.onNext(t); + if (++count == j) { + count = 0; + requestMore(j); + } + } + }; + + Observable.range(1, 25000) + .observeOn(Schedulers.computation(), i) + .switchMap(new Func1>() { + @Override + public Observable call(Integer v) { + return Observable.range(1, 1000).observeOn(Schedulers.computation(), j) + .doOnError(new Action1() { + @Override + public void call(Throwable e) { + q.add(e); + } + }); + } + }) + .timeout(10, TimeUnit.SECONDS) + .subscribe(ts); + + ts.awaitTerminalEvent(30, TimeUnit.SECONDS); + if (!q.isEmpty()) { + throw new AssertionError("Dropped exceptions", new CompositeException(q)); + } + ts.assertNoErrors(); + if (ts.getOnCompletedEvents().size() == 0) { + fail("switchAsyncHeavily timed out @ " + j + " (" + ts.getOnNextEvents().size() + " onNexts received)"); + } + } + } } From 1b70eb0afc2123658ddd5bb9fecbdfdf02aeabaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Karnok?= Date: Thu, 7 Apr 2016 17:37:31 +0200 Subject: [PATCH 015/294] 1.x: fix concatMap scalar source behavior --- .../internal/operators/OnSubscribeConcatMap.java | 4 +++- .../rx/internal/operators/OperatorConcatTest.java | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/java/rx/internal/operators/OnSubscribeConcatMap.java b/src/main/java/rx/internal/operators/OnSubscribeConcatMap.java index 001058763b..6f0e8435b4 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeConcatMap.java +++ b/src/main/java/rx/internal/operators/OnSubscribeConcatMap.java @@ -273,6 +273,8 @@ void drain() { if (source instanceof ScalarSynchronousObservable) { ScalarSynchronousObservable scalarSource = (ScalarSynchronousObservable) source; + active = true; + arbiter.setProducer(new ConcatMapInnerScalarProducer(scalarSource.get(), this)); } else { ConcatMapInnerSubscriber innerSubscriber = new ConcatMapInnerSubscriber(this); @@ -352,7 +354,7 @@ public ConcatMapInnerScalarProducer(R value, ConcatMapSubscriber parent) { @Override public void request(long n) { - if (!once) { + if (!once && n > 0L) { once = true; ConcatMapSubscriber p = parent; p.innerNext(value); diff --git a/src/test/java/rx/internal/operators/OperatorConcatTest.java b/src/test/java/rx/internal/operators/OperatorConcatTest.java index 65aa1f5307..8a39b16f9f 100644 --- a/src/test/java/rx/internal/operators/OperatorConcatTest.java +++ b/src/test/java/rx/internal/operators/OperatorConcatTest.java @@ -850,4 +850,18 @@ public Observable call(Integer t) { } } + @Test + public void scalarAndRangeBackpressured() { + TestSubscriber ts = TestSubscriber.create(0); + + Observable.just(1).concatWith(Observable.range(2, 3)).subscribe(ts); + + ts.assertNoValues(); + + ts.requestMore(5); + + ts.assertValues(1, 2, 3, 4); + ts.assertCompleted(); + ts.assertNoErrors(); + } } \ No newline at end of file From eff6bb03eb333921bcb132159a92f2cf6717681d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Karnok?= Date: Thu, 7 Apr 2016 17:51:00 +0200 Subject: [PATCH 016/294] More tests and fix empty() case as well --- .../operators/OnSubscribeConcatMap.java | 12 +++-- .../operators/OperatorConcatTest.java | 46 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/main/java/rx/internal/operators/OnSubscribeConcatMap.java b/src/main/java/rx/internal/operators/OnSubscribeConcatMap.java index 6f0e8435b4..c2799df758 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeConcatMap.java +++ b/src/main/java/rx/internal/operators/OnSubscribeConcatMap.java @@ -220,7 +220,7 @@ void drain() { final int delayErrorMode = this.delayErrorMode; - do { + for (;;) { if (actual.isUnsubscribed()) { return; } @@ -288,11 +288,17 @@ void drain() { return; } } + request(1); + } else { + request(1); + continue; } - request(1); } } - } while (wip.decrementAndGet() != 0); + if (wip.decrementAndGet() == 0) { + break; + } + } } void drainError(Throwable mapperError) { diff --git a/src/test/java/rx/internal/operators/OperatorConcatTest.java b/src/test/java/rx/internal/operators/OperatorConcatTest.java index 8a39b16f9f..a824374659 100644 --- a/src/test/java/rx/internal/operators/OperatorConcatTest.java +++ b/src/test/java/rx/internal/operators/OperatorConcatTest.java @@ -864,4 +864,50 @@ public void scalarAndRangeBackpressured() { ts.assertCompleted(); ts.assertNoErrors(); } + + @Test + public void scalarAndEmptyBackpressured() { + TestSubscriber ts = TestSubscriber.create(0); + + Observable.just(1).concatWith(Observable.empty()).subscribe(ts); + + ts.assertNoValues(); + + ts.requestMore(5); + + ts.assertValue(1); + ts.assertCompleted(); + ts.assertNoErrors(); + } + + @Test + public void rangeAndEmptyBackpressured() { + TestSubscriber ts = TestSubscriber.create(0); + + Observable.range(1, 2).concatWith(Observable.empty()).subscribe(ts); + + ts.assertNoValues(); + + ts.requestMore(5); + + ts.assertValues(1, 2); + ts.assertCompleted(); + ts.assertNoErrors(); + } + + @Test + public void emptyAndScalarBackpressured() { + TestSubscriber ts = TestSubscriber.create(0); + + Observable.empty().concatWith(Observable.just(1)).subscribe(ts); + + ts.assertNoValues(); + + ts.requestMore(5); + + ts.assertValue(1); + ts.assertCompleted(); + ts.assertNoErrors(); + } + } \ No newline at end of file From ebf0e3f36519ffe471c63deb2c02b3636ef79900 Mon Sep 17 00:00:00 2001 From: Dave Moten Date: Fri, 8 Apr 2016 05:30:39 +1000 Subject: [PATCH 017/294] fix undesired execution in ExecutorScheduler worker when unsubscribed --- .../java/rx/schedulers/ExecutorScheduler.java | 12 +++++++- .../rx/schedulers/ExecutorSchedulerTest.java | 30 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/main/java/rx/schedulers/ExecutorScheduler.java b/src/main/java/rx/schedulers/ExecutorScheduler.java index 8e5c9bf22e..70f217e49b 100644 --- a/src/main/java/rx/schedulers/ExecutorScheduler.java +++ b/src/main/java/rx/schedulers/ExecutorScheduler.java @@ -96,11 +96,20 @@ public Subscription schedule(Action0 action) { @Override public void run() { do { + if (tasks.isUnsubscribed()) { + queue.clear(); + return; + } + ScheduledAction sa = queue.poll(); + if (sa == null) { + return; + } + if (!sa.isUnsubscribed()) { sa.run(); } - } while (wip.decrementAndGet() > 0); + } while (wip.decrementAndGet() != 0); } @Override @@ -170,6 +179,7 @@ public boolean isUnsubscribed() { @Override public void unsubscribe() { tasks.unsubscribe(); + queue.clear(); } } diff --git a/src/test/java/rx/schedulers/ExecutorSchedulerTest.java b/src/test/java/rx/schedulers/ExecutorSchedulerTest.java index ed4e03213d..0777208cab 100644 --- a/src/test/java/rx/schedulers/ExecutorSchedulerTest.java +++ b/src/test/java/rx/schedulers/ExecutorSchedulerTest.java @@ -18,9 +18,11 @@ import static org.junit.Assert.*; import java.lang.management.*; +import java.util.Queue; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Assert; import org.junit.Test; import rx.*; @@ -275,4 +277,32 @@ public void call() { assertFalse(w.tasks.hasSubscriptions()); } + + @Test + public void workerUnderConcurrentUnsubscribeShouldNotAllowLaterTasksToRunDueToUnsubscriptionRace() { + Scheduler scheduler = Schedulers.from(Executors.newFixedThreadPool(1)); + for (int i = 0; i< 1000; i++) { + Worker worker = scheduler.createWorker(); + final Queue q = new ConcurrentLinkedQueue(); + Action0 action1 = new Action0() { + + @Override + public void call() { + q.add(1); + }}; + Action0 action2 = new Action0() { + + @Override + public void call() { + q.add(2); + }}; + worker.schedule(action1); + worker.schedule(action2); + worker.unsubscribe(); + if (q.size()==1 && q.poll() == 2) { + //expect a queue of 1,2 or 1. If queue is just 2 then we have a problem! + Assert.fail("wrong order on loop " + i); + } + } + } } From 53c31cde6906bad5825b2b65c91c1d268a6a32b5 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Fri, 8 Apr 2016 22:56:14 +0200 Subject: [PATCH 018/294] 1.x: observeOn now replenishes with constant rate (#3795) --- .../internal/operators/OperatorObserveOn.java | 45 ++++++++-------- .../operators/OperatorObserveOnTest.java | 51 ++++++++++++++++++- .../rx/observables/AsyncOnSubscribeTest.java | 7 +-- 3 files changed, 75 insertions(+), 28 deletions(-) diff --git a/src/main/java/rx/internal/operators/OperatorObserveOn.java b/src/main/java/rx/internal/operators/OperatorObserveOn.java index 2a7c7684dd..1720e1dfe2 100644 --- a/src/main/java/rx/internal/operators/OperatorObserveOn.java +++ b/src/main/java/rx/internal/operators/OperatorObserveOn.java @@ -83,7 +83,8 @@ private static final class ObserveOnSubscriber extends Subscriber implemen final NotificationLite on; final boolean delayError; final Queue queue; - final int bufferSize; + /** The emission threshold that should trigger a replenishing request. */ + final int limit; // the status of the current stream volatile boolean finished; @@ -97,6 +98,9 @@ private static final class ObserveOnSubscriber extends Subscriber implemen * reading finished (acquire). */ Throwable error; + + /** Remembers how many elements have been emitted before the requests run out. */ + long emitted; // do NOT pass the Subscriber through to couple the subscription chain ... unsubscribing on the parent should // not prevent anything downstream from consuming, which will happen if the Subscription is chained @@ -105,12 +109,16 @@ public ObserveOnSubscriber(Scheduler scheduler, Subscriber child, boo this.recursiveScheduler = scheduler.createWorker(); this.delayError = delayError; this.on = NotificationLite.instance(); - this.bufferSize = (bufferSize > 0) ? bufferSize : RxRingBuffer.SIZE; + int calculatedSize = (bufferSize > 0) ? bufferSize : RxRingBuffer.SIZE; + // this formula calculates the 75% of the bufferSize, rounded up to the next integer + this.limit = calculatedSize - (calculatedSize >> 2); if (UnsafeAccess.isUnsafeAvailable()) { - queue = new SpscArrayQueue(this.bufferSize); + queue = new SpscArrayQueue(calculatedSize); } else { - queue = new SpscAtomicArrayQueue(this.bufferSize); + queue = new SpscAtomicArrayQueue(calculatedSize); } + // signal that this is an async operator capable of receiving this many + request(calculatedSize); } void init() { @@ -133,12 +141,6 @@ public void request(long n) { localChild.add(this); } - @Override - public void onStart() { - // signal that this is an async operator capable of receiving this many - request(this.bufferSize); - } - @Override public void onNext(final T t) { if (isUnsubscribed() || finished) { @@ -180,9 +182,8 @@ protected void schedule() { // only execute this from schedule() @Override public void call() { - long emitted = 0L; - long missed = 1L; + long currentEmission = emitted; // these are accessed in a tight loop around atomics so // loading them into local variables avoids the mandatory re-reading @@ -197,7 +198,6 @@ public void call() { for (;;) { long requestAmount = requested.get(); - long currentEmission = 0L; while (requestAmount != currentEmission) { boolean done = finished; @@ -215,7 +215,11 @@ public void call() { localChild.onNext(localOn.getValue(v)); currentEmission++; - emitted++; + if (currentEmission == limit) { + requestAmount = BackpressureUtils.produced(requested, currentEmission); + request(currentEmission); + currentEmission = 0L; + } } if (requestAmount == currentEmission) { @@ -223,20 +227,13 @@ public void call() { return; } } - - if (currentEmission != 0L) { - BackpressureUtils.produced(requested, currentEmission); - } - + + emitted = currentEmission; missed = counter.addAndGet(-missed); if (missed == 0L) { break; } } - - if (emitted != 0L) { - request(emitted); - } } boolean checkTerminated(boolean done, boolean isEmpty, Subscriber a, Queue q) { @@ -285,4 +282,4 @@ boolean checkTerminated(boolean done, boolean isEmpty, Subscriber a, return false; } } -} +} \ No newline at end of file diff --git a/src/test/java/rx/internal/operators/OperatorObserveOnTest.java b/src/test/java/rx/internal/operators/OperatorObserveOnTest.java index 323f74b786..29c1a4a8d5 100644 --- a/src/test/java/rx/internal/operators/OperatorObserveOnTest.java +++ b/src/test/java/rx/internal/operators/OperatorObserveOnTest.java @@ -864,7 +864,7 @@ public void testErrorDelayedAsync() { @Test public void requestExactCompletesImmediately() { -TestSubscriber ts = TestSubscriber.create(0); + TestSubscriber ts = TestSubscriber.create(0); TestScheduler test = Schedulers.test(); @@ -884,4 +884,53 @@ public void requestExactCompletesImmediately() { ts.assertNoErrors(); ts.assertCompleted(); } + + @Test + public void fixedReplenishPattern() { + TestSubscriber ts = TestSubscriber.create(0); + + TestScheduler test = Schedulers.test(); + + final List requests = new ArrayList(); + + Observable.range(1, 100) + .doOnRequest(new Action1() { + @Override + public void call(Long v) { + requests.add(v); + } + }) + .observeOn(test, 16).subscribe(ts); + + test.advanceTimeBy(1, TimeUnit.SECONDS); + ts.requestMore(20); + test.advanceTimeBy(1, TimeUnit.SECONDS); + ts.requestMore(10); + test.advanceTimeBy(1, TimeUnit.SECONDS); + ts.requestMore(50); + test.advanceTimeBy(1, TimeUnit.SECONDS); + ts.requestMore(35); + test.advanceTimeBy(1, TimeUnit.SECONDS); + + ts.assertValueCount(100); + ts.assertCompleted(); + ts.assertNoErrors(); + + assertEquals(Arrays.asList(16L, 12L, 12L, 12L, 12L, 12L, 12L, 12L, 12L), requests); + } + + @Test + public void bufferSizesWork() { + for (int i = 1; i <= 1024; i = i * 2) { + TestSubscriber ts = TestSubscriber.create(); + + Observable.range(1, 1000 * 1000).observeOn(Schedulers.computation(), i) + .subscribe(ts); + + ts.awaitTerminalEvent(); + ts.assertValueCount(1000 * 1000); + ts.assertCompleted(); + ts.assertNoErrors(); + } + } } diff --git a/src/test/java/rx/observables/AsyncOnSubscribeTest.java b/src/test/java/rx/observables/AsyncOnSubscribeTest.java index 633d229921..cd74520971 100644 --- a/src/test/java/rx/observables/AsyncOnSubscribeTest.java +++ b/src/test/java/rx/observables/AsyncOnSubscribeTest.java @@ -383,7 +383,8 @@ public void call() { }})); break; case 2: - observer.onNext(Observable.never() + observer.onNext(Observable.just(1) + .concatWith(Observable.never()) .subscribeOn(scheduler) .doOnUnsubscribe(new Action0(){ @Override @@ -397,7 +398,7 @@ public void call() { return state + 1; }}); Subscription subscription = Observable.create(os) - .observeOn(scheduler) + .observeOn(scheduler, 1) .subscribe(subscriber); sub.set(subscription); subscriber.assertNoValues(); @@ -465,4 +466,4 @@ public Integer call(Integer state, Long requested, Observer Date: Fri, 8 Apr 2016 23:39:57 +0200 Subject: [PATCH 019/294] 1.x: fix takeLast() backpressure (#3839) --- .../internal/operators/BackpressureUtils.java | 99 ++++++++++-- .../internal/operators/OperatorTakeLast.java | 95 +++++++----- .../operators/OperatorTakeLastTimed.java | 142 +++++++++++------- .../operators/TakeLastQueueProducer.java | 124 --------------- .../operators/OperatorTakeLastTest.java | 98 ++++++++++-- .../operators/OperatorTakeLastTimedTest.java | 80 +++++++++- 6 files changed, 396 insertions(+), 242 deletions(-) delete mode 100644 src/main/java/rx/internal/operators/TakeLastQueueProducer.java diff --git a/src/main/java/rx/internal/operators/BackpressureUtils.java b/src/main/java/rx/internal/operators/BackpressureUtils.java index 3d199567c6..4a2fa90f48 100644 --- a/src/main/java/rx/internal/operators/BackpressureUtils.java +++ b/src/main/java/rx/internal/operators/BackpressureUtils.java @@ -19,6 +19,8 @@ import java.util.concurrent.atomic.*; import rx.Subscriber; +import rx.functions.Func1; +import rx.internal.util.UtilityFunctions; /** * Utility functions for use with backpressure. @@ -140,6 +142,59 @@ public static long addCap(long a, long b) { * @param actual the subscriber to receive the values */ public static void postCompleteDone(AtomicLong requested, Queue queue, Subscriber actual) { + postCompleteDone(requested, queue, actual, UtilityFunctions.identity()); + } + + /** + * Accumulates requests (validated) and handles the completed mode draining of the queue based on the requests. + * + *

+ * Post-completion backpressure handles the case when a source produces values based on + * requests when it is active but more values are available even after its completion. + * In this case, the onCompleted() can't just emit the contents of the queue but has to + * coordinate with the requested amounts. This requires two distinct modes: active and + * completed. In active mode, requests flow through and the queue is not accessed but + * in completed mode, requests no-longer reach the upstream but help in draining the queue. + * + * @param the value type to emit + * @param requested the holder of current requested amount + * @param n the value requested; + * @param queue the queue holding values to be emitted after completion + * @param actual the subscriber to receive the values + * @return true if in the active mode and the request amount of n can be relayed to upstream, false if + * in the post-completed mode and the queue is draining. + */ + public static boolean postCompleteRequest(AtomicLong requested, long n, Queue queue, Subscriber actual) { + return postCompleteRequest(requested, n, queue, actual, UtilityFunctions.identity()); + } + + /** + * Signals the completion of the main sequence and switches to post-completion replay mode + * and allows exit transformation on the queued values. + * + *

+ * Don't modify the queue after calling this method! + * + *

+ * Post-completion backpressure handles the case when a source produces values based on + * requests when it is active but more values are available even after its completion. + * In this case, the onCompleted() can't just emit the contents of the queue but has to + * coordinate with the requested amounts. This requires two distinct modes: active and + * completed. In active mode, requests flow through and the queue is not accessed but + * in completed mode, requests no-longer reach the upstream but help in draining the queue. + *

+ * The algorithm utilizes the most significant bit (bit 63) of a long value (AtomicLong) since + * request amount only goes up to Long.MAX_VALUE (bits 0-62) and negative values aren't + * allowed. + * + * @param the value type in the queue + * @param the value type to emit + * @param requested the holder of current requested amount + * @param queue the queue holding values to be emitted after completion + * @param actual the subscriber to receive the values + * @param exitTransform the transformation to apply on the dequeued value to get the value to be emitted + */ + public static void postCompleteDone(AtomicLong requested, Queue queue, Subscriber actual, Func1 exitTransform) { for (;;) { long r = requested.get(); @@ -156,7 +211,7 @@ public static void postCompleteDone(AtomicLong requested, Queue queue, Su // are requests available start draining the queue if (r != 0L) { // if the switch happened when there was outstanding requests, start draining - postCompleteDrain(requested, queue, actual); + postCompleteDrain(requested, queue, actual, exitTransform); } return; } @@ -164,7 +219,8 @@ public static void postCompleteDone(AtomicLong requested, Queue queue, Su } /** - * Accumulates requests (validated) and handles the completed mode draining of the queue based on the requests. + * Accumulates requests (validated) and handles the completed mode draining of the queue based on the requests + * and allows exit transformation on the queued values. * *

* Post-completion backpressure handles the case when a source produces values based on @@ -174,15 +230,17 @@ public static void postCompleteDone(AtomicLong requested, Queue queue, Su * completed. In active mode, requests flow through and the queue is not accessed but * in completed mode, requests no-longer reach the upstream but help in draining the queue. * - * @param the value type to emit + * @param the value type in the queue + * @param the value type to emit * @param requested the holder of current requested amount * @param n the value requested; * @param queue the queue holding values to be emitted after completion * @param actual the subscriber to receive the values + * @param exitTransform the transformation to apply on the dequeued value to get the value to be emitted * @return true if in the active mode and the request amount of n can be relayed to upstream, false if * in the post-completed mode and the queue is draining. */ - public static boolean postCompleteRequest(AtomicLong requested, long n, Queue queue, Subscriber actual) { + public static boolean postCompleteRequest(AtomicLong requested, long n, Queue queue, Subscriber actual, Func1 exitTransform) { if (n < 0L) { throw new IllegalArgumentException("n >= 0 required but it was " + n); } @@ -209,7 +267,7 @@ public static boolean postCompleteRequest(AtomicLong requested, long n, Queu // if there was no outstanding request before and in // the post-completed state, start draining if (r == COMPLETED_MASK) { - postCompleteDrain(requested, queue, actual); + postCompleteDrain(requested, queue, actual, exitTransform); return false; } // returns true for active mode and false if the completed flag was set @@ -219,16 +277,37 @@ public static boolean postCompleteRequest(AtomicLong requested, long n, Queu } /** - * Drains the queue based on the outstanding requests in post-completed mode (only!). + * Drains the queue based on the outstanding requests in post-completed mode (only!) + * and allows exit transformation on the queued values. * - * @param the value type to emit + * @param the value type in the queue + * @param the value type to emit * @param requested the holder of current requested amount * @param queue the queue holding values to be emitted after completion - * @param actual the subscriber to receive the values + * @param subscriber the subscriber to receive the values + * @param exitTransform the transformation to apply on the dequeued value to get the value to be emitted */ - static void postCompleteDrain(AtomicLong requested, Queue queue, Subscriber subscriber) { + static void postCompleteDrain(AtomicLong requested, Queue queue, Subscriber subscriber, Func1 exitTransform) { long r = requested.get(); + + // Run on a fast-path if the downstream is unbounded + if (r == Long.MAX_VALUE) { + for (;;) { + if (subscriber.isUnsubscribed()) { + return; + } + + T v = queue.poll(); + + if (v == null) { + subscriber.onCompleted(); + return; + } + + subscriber.onNext(exitTransform.call(v)); + } + } /* * Since we are supposed to be in the post-complete state, * requested will have its top bit set. @@ -264,7 +343,7 @@ static void postCompleteDrain(AtomicLong requested, Queue queue, Subscrib return; } - subscriber.onNext(v); + subscriber.onNext(exitTransform.call(v)); e++; } diff --git a/src/main/java/rx/internal/operators/OperatorTakeLast.java b/src/main/java/rx/internal/operators/OperatorTakeLast.java index 2812c4e87c..77f8c93993 100644 --- a/src/main/java/rx/internal/operators/OperatorTakeLast.java +++ b/src/main/java/rx/internal/operators/OperatorTakeLast.java @@ -16,15 +16,18 @@ package rx.internal.operators; import java.util.ArrayDeque; -import java.util.Deque; +import java.util.concurrent.atomic.AtomicLong; +import rx.*; import rx.Observable.Operator; -import rx.Subscriber; +import rx.functions.Func1; /** * Returns an Observable that emits the at most the last count items emitted by the source Observable. *

* + * + * @param the value type */ public final class OperatorTakeLast implements Operator { @@ -39,44 +42,62 @@ public OperatorTakeLast(int count) { @Override public Subscriber call(final Subscriber subscriber) { - final Deque deque = new ArrayDeque(); - final NotificationLite notification = NotificationLite.instance(); - final TakeLastQueueProducer producer = new TakeLastQueueProducer(notification, deque, subscriber); - subscriber.setProducer(producer); - - return new Subscriber(subscriber) { - - // no backpressure up as it wants to receive and discard all but the last + final TakeLastSubscriber parent = new TakeLastSubscriber(subscriber, count); + + subscriber.add(parent); + subscriber.setProducer(new Producer() { @Override - public void onStart() { - // we do this to break the chain of the child subscriber being passed through - request(Long.MAX_VALUE); + public void request(long n) { + parent.requestMore(n); } - - @Override - public void onCompleted() { - deque.offer(notification.completed()); - producer.startEmitting(); - } - - @Override - public void onError(Throwable e) { - deque.clear(); - subscriber.onError(e); + }); + + return parent; + } + + static final class TakeLastSubscriber extends Subscriber implements Func1 { + final Subscriber actual; + final AtomicLong requested; + final ArrayDeque queue; + final int count; + final NotificationLite nl; + + public TakeLastSubscriber(Subscriber actual, int count) { + this.actual = actual; + this.count = count; + this.requested = new AtomicLong(); + this.queue = new ArrayDeque(); + this.nl = NotificationLite.instance(); + } + + @Override + public void onNext(T t) { + if (queue.size() == count) { + queue.poll(); } - - @Override - public void onNext(T value) { - if (count == 0) { - // If count == 0, we do not need to put value into deque and - // remove it at once. We can ignore the value directly. - return; - } - if (deque.size() == count) { - deque.removeFirst(); - } - deque.offerLast(notification.next(value)); + queue.offer(nl.next(t)); + } + + @Override + public void onError(Throwable e) { + queue.clear(); + actual.onError(e); + } + + @Override + public void onCompleted() { + BackpressureUtils.postCompleteDone(requested, queue, actual, this); + } + + @Override + public T call(Object t) { + return nl.getValue(t); + } + + void requestMore(long n) { + if (n > 0L) { + BackpressureUtils.postCompleteRequest(requested, n, queue, actual, this); } - }; + } } } diff --git a/src/main/java/rx/internal/operators/OperatorTakeLastTimed.java b/src/main/java/rx/internal/operators/OperatorTakeLastTimed.java index ec7cc12493..383f41715c 100644 --- a/src/main/java/rx/internal/operators/OperatorTakeLastTimed.java +++ b/src/main/java/rx/internal/operators/OperatorTakeLastTimed.java @@ -15,18 +15,20 @@ */ package rx.internal.operators; -import rx.Observable.Operator; -import rx.Scheduler; -import rx.Subscriber; - import java.util.ArrayDeque; -import java.util.Deque; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import rx.*; +import rx.Observable.Operator; +import rx.functions.Func1; /** * Returns an Observable that emits the last count items emitted by the source Observable. *

* + * + * @param the value type */ public final class OperatorTakeLastTimed implements Operator { @@ -51,60 +53,92 @@ public OperatorTakeLastTimed(int count, long time, TimeUnit unit, Scheduler sche @Override public Subscriber call(final Subscriber subscriber) { - final Deque buffer = new ArrayDeque(); - final Deque timestampBuffer = new ArrayDeque(); - final NotificationLite notification = NotificationLite.instance(); - final TakeLastQueueProducer producer = new TakeLastQueueProducer(notification, buffer, subscriber); - subscriber.setProducer(producer); - return new Subscriber(subscriber) { - - protected void runEvictionPolicy(long now) { - // trim size - while (count >= 0 && buffer.size() > count) { - timestampBuffer.pollFirst(); - buffer.pollFirst(); - } - // remove old entries - while (!buffer.isEmpty()) { - long v = timestampBuffer.peekFirst(); - if (v < now - ageMillis) { - timestampBuffer.pollFirst(); - buffer.pollFirst(); - } else { - break; - } - } - } - - // no backpressure up as it wants to receive and discard all but the last + final TakeLastTimedSubscriber parent = new TakeLastTimedSubscriber(subscriber, count, ageMillis, scheduler); + + subscriber.add(parent); + subscriber.setProducer(new Producer() { @Override - public void onStart() { - // we do this to break the chain of the child subscriber being passed through - request(Long.MAX_VALUE); - } - - @Override - public void onNext(T args) { - long t = scheduler.now(); - timestampBuffer.add(t); - buffer.add(notification.next(args)); - runEvictionPolicy(t); + public void request(long n) { + parent.requestMore(n); } + }); + + return parent; + } + + static final class TakeLastTimedSubscriber extends Subscriber implements Func1 { + final Subscriber actual; + final long ageMillis; + final Scheduler scheduler; + final int count; + final AtomicLong requested; + final ArrayDeque queue; + final ArrayDeque queueTimes; + final NotificationLite nl; - @Override - public void onError(Throwable e) { - timestampBuffer.clear(); - buffer.clear(); - subscriber.onError(e); + public TakeLastTimedSubscriber(Subscriber actual, int count, long ageMillis, Scheduler scheduler) { + this.actual = actual; + this.count = count; + this.ageMillis = ageMillis; + this.scheduler = scheduler; + this.requested = new AtomicLong(); + this.queue = new ArrayDeque(); + this.queueTimes = new ArrayDeque(); + this.nl = NotificationLite.instance(); + } + + @Override + public void onNext(T t) { + if (count != 0) { + long now = scheduler.now(); + + if (queue.size() == count) { + queue.poll(); + queueTimes.poll(); + } + + evictOld(now); + + queue.offer(nl.next(t)); + queueTimes.offer(now); } + } - @Override - public void onCompleted() { - runEvictionPolicy(scheduler.now()); - timestampBuffer.clear(); - buffer.offer(notification.completed()); - producer.startEmitting(); + protected void evictOld(long now) { + long minTime = now - ageMillis; + for (;;) { + Long time = queueTimes.peek(); + if (time == null || time >= minTime) { + break; + } + queue.poll(); + queueTimes.poll(); } - }; + } + + @Override + public void onError(Throwable e) { + queue.clear(); + queueTimes.clear(); + actual.onError(e); + } + + @Override + public void onCompleted() { + evictOld(scheduler.now()); + + queueTimes.clear(); + + BackpressureUtils.postCompleteDone(requested, queue, actual, this); + } + + @Override + public T call(Object t) { + return nl.getValue(t); + } + + void requestMore(long n) { + BackpressureUtils.postCompleteRequest(requested, n, queue, actual, this); + } } } diff --git a/src/main/java/rx/internal/operators/TakeLastQueueProducer.java b/src/main/java/rx/internal/operators/TakeLastQueueProducer.java deleted file mode 100644 index 664dfd0e3a..0000000000 --- a/src/main/java/rx/internal/operators/TakeLastQueueProducer.java +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.internal.operators; - - -import java.util.Deque; -import java.util.concurrent.atomic.AtomicLong; - -import rx.Producer; -import rx.Subscriber; -import rx.exceptions.Exceptions; - -final class TakeLastQueueProducer extends AtomicLong implements Producer { - - private final NotificationLite notification; - private final Deque deque; - private final Subscriber subscriber; - private volatile boolean emittingStarted = false; - - public TakeLastQueueProducer(NotificationLite n, Deque q, Subscriber subscriber) { - this.notification = n; - this.deque = q; - this.subscriber = subscriber; - } - - void startEmitting() { - if (!emittingStarted) { - emittingStarted = true; - emit(0); // start emitting - } - } - - @Override - public void request(long n) { - if (get() == Long.MAX_VALUE) { - return; - } - long _c; - if (n == Long.MAX_VALUE) { - _c = getAndSet(Long.MAX_VALUE); - } else { - _c = BackpressureUtils.getAndAddRequest(this, n); - } - if (!emittingStarted) { - // we haven't started yet, so record what was requested and return - return; - } - emit(_c); - } - - void emit(long previousRequested) { - if (get() == Long.MAX_VALUE) { - // fast-path without backpressure - if (previousRequested == 0) { - try { - for (Object value : deque) { - if (subscriber.isUnsubscribed()) - return; - notification.accept(subscriber, value); - } - } catch (Throwable e) { - Exceptions.throwOrReport(e, subscriber); - } finally { - deque.clear(); - } - } else { - // backpressure path will handle Long.MAX_VALUE and emit the rest events. - } - } else { - // backpressure is requested - if (previousRequested == 0) { - while (true) { - /* - * This complicated logic is done to avoid touching the volatile `requested` value - * during the loop itself. If it is touched during the loop the performance is impacted significantly. - */ - long numToEmit = get(); - int emitted = 0; - Object o; - while (--numToEmit >= 0 && (o = deque.poll()) != null) { - if (subscriber.isUnsubscribed()) { - return; - } - if (notification.accept(subscriber, o)) { - // terminal event - return; - } else { - emitted++; - } - } - for (; ; ) { - long oldRequested = get(); - long newRequested = oldRequested - emitted; - if (oldRequested == Long.MAX_VALUE) { - // became unbounded during the loop - // continue the outer loop to emit the rest events. - break; - } - if (compareAndSet(oldRequested, newRequested)) { - if (newRequested == 0) { - // we're done emitting the number requested so return - return; - } - break; - } - } - } - } - } - } -} diff --git a/src/test/java/rx/internal/operators/OperatorTakeLastTest.java b/src/test/java/rx/internal/operators/OperatorTakeLastTest.java index c3297db0a0..154b3067b0 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeLastTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeLastTest.java @@ -17,28 +17,24 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.Test; +import static org.mockito.Mockito.*; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import org.junit.*; import org.mockito.InOrder; import rx.Observable; import rx.Observer; +import rx.Scheduler.Worker; import rx.Subscriber; -import rx.functions.Func1; -import rx.internal.util.RxRingBuffer; -import rx.internal.util.UtilityFunctions; +import rx.functions.*; +import rx.internal.util.*; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; public class OperatorTakeLastTest { @@ -323,4 +319,76 @@ public void onNext(Integer t) { }}); assertEquals(50, list.size()); } + + @Test(timeout = 30000) // original could get into an infinite loop + public void completionRequestRace() { + Worker w = Schedulers.computation().createWorker(); + try { + final int n = 1000; + for (int i = 0; i < 25000; i++) { + if (i % 1000 == 0) { + System.out.println("completionRequestRace >> " + i); + } + PublishSubject ps = PublishSubject.create(); + final TestSubscriber ts = new TestSubscriber(0); + + ps.takeLast(n).subscribe(ts); + + for (int j = 0; j < n; j++) { + ps.onNext(j); + } + + final AtomicBoolean go = new AtomicBoolean(); + + w.schedule(new Action0() { + @Override + public void call() { + while (!go.get()); + ts.requestMore(n + 1); + } + }); + + go.set(true); + ps.onCompleted(); + + ts.awaitTerminalEvent(1, TimeUnit.SECONDS); + + ts.assertValueCount(n); + ts.assertNoErrors(); + ts.assertCompleted(); + + List list = ts.getOnNextEvents(); + for (int j = 0; j < n; j++) { + Assert.assertEquals(j, list.get(j).intValue()); + } + } + } finally { + w.unsubscribe(); + } + } + + @Test + public void nullElements() { + TestSubscriber ts = new TestSubscriber(0); + + Observable.from(new Integer[] { 1, null, 2}).takeLast(4) + .subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + + ts.requestMore(1); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertNotCompleted(); + + ts.requestMore(2); + + ts.assertValues(1, null, 2); + ts.assertCompleted(); + ts.assertNoErrors(); + } + } diff --git a/src/test/java/rx/internal/operators/OperatorTakeLastTimedTest.java b/src/test/java/rx/internal/operators/OperatorTakeLastTimedTest.java index 800a2cd673..c227339702 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeLastTimedTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeLastTimedTest.java @@ -22,15 +22,20 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Test; +import org.junit.*; import org.mockito.InOrder; import rx.Observable; import rx.Observer; +import rx.Scheduler.Worker; import rx.exceptions.TestException; -import rx.schedulers.TestScheduler; +import rx.functions.Action0; +import rx.observers.TestSubscriber; +import rx.schedulers.*; import rx.subjects.PublishSubject; public class OperatorTakeLastTimedTest { @@ -208,4 +213,75 @@ public void takeLastTimedWithZeroCapacity() { verify(o, never()).onNext(any()); verify(o, never()).onError(any(Throwable.class)); } + + @Test(timeout = 30000) // original could get into an infinite loop + public void completionRequestRace() { + Worker w = Schedulers.computation().createWorker(); + try { + final int n = 1000; + for (int i = 0; i < 25000; i++) { + if (i % 1000 == 0) { + System.out.println("completionRequestRace >> " + i); + } + PublishSubject ps = PublishSubject.create(); + final TestSubscriber ts = new TestSubscriber(0); + + ps.takeLast(n, 1, TimeUnit.DAYS).subscribe(ts); + + for (int j = 0; j < n; j++) { + ps.onNext(j); + } + + final AtomicBoolean go = new AtomicBoolean(); + + w.schedule(new Action0() { + @Override + public void call() { + while (!go.get()); + ts.requestMore(n + 1); + } + }); + + go.set(true); + ps.onCompleted(); + + ts.awaitTerminalEvent(1, TimeUnit.SECONDS); + + ts.assertValueCount(n); + ts.assertNoErrors(); + ts.assertCompleted(); + + List list = ts.getOnNextEvents(); + for (int j = 0; j < n; j++) { + Assert.assertEquals(j, list.get(j).intValue()); + } + } + } finally { + w.unsubscribe(); + } + } + + @Test + public void nullElements() { + TestSubscriber ts = new TestSubscriber(0); + + Observable.from(new Integer[] { 1, null, 2}).takeLast(4, 1, TimeUnit.DAYS) + .subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + + ts.requestMore(1); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertNotCompleted(); + + ts.requestMore(2); + + ts.assertValues(1, null, 2); + ts.assertCompleted(); + ts.assertNoErrors(); + } } From b126c6ce7d045f6b7499f7a51905434f513c6fda Mon Sep 17 00:00:00 2001 From: David Karnok Date: Fri, 8 Apr 2016 23:45:03 +0200 Subject: [PATCH 020/294] 1.x: fix delaySubscription(Observable) unsubscription before triggered (#3845) --- .../OnSubscribeDelaySubscriptionOther.java | 9 ++- ...OnSubscribeDelaySubscriptionOtherTest.java | 67 ++++++++++++++++++- .../internal/operators/OperatorDelayTest.java | 51 ++++++++++++-- 3 files changed, 118 insertions(+), 9 deletions(-) diff --git a/src/main/java/rx/internal/operators/OnSubscribeDelaySubscriptionOther.java b/src/main/java/rx/internal/operators/OnSubscribeDelaySubscriptionOther.java index 2a8b7e1601..dc3146d7e6 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeDelaySubscriptionOther.java +++ b/src/main/java/rx/internal/operators/OnSubscribeDelaySubscriptionOther.java @@ -20,7 +20,7 @@ import rx.Observable.OnSubscribe; import rx.observers.Subscribers; import rx.plugins.*; -import rx.subscriptions.SerialSubscription; +import rx.subscriptions.*; /** * Delays the subscription to the main source until the other @@ -39,9 +39,12 @@ public OnSubscribeDelaySubscriptionOther(Observable main, Observabl @Override public void call(Subscriber t) { + final SerialSubscription serial = new SerialSubscription(); + + t.add(serial); + final Subscriber child = Subscribers.wrap(t); - final SerialSubscription serial = new SerialSubscription(); Subscriber otherSubscriber = new Subscriber() { boolean done; @@ -66,7 +69,7 @@ public void onCompleted() { return; } done = true; - serial.set(child); + serial.set(Subscriptions.unsubscribed()); main.unsafeSubscribe(child); } diff --git a/src/test/java/rx/internal/operators/OnSubscribeDelaySubscriptionOtherTest.java b/src/test/java/rx/internal/operators/OnSubscribeDelaySubscriptionOtherTest.java index e157a788e5..b44b720b41 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeDelaySubscriptionOtherTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeDelaySubscriptionOtherTest.java @@ -16,7 +16,7 @@ package rx.internal.operators; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import org.junit.*; @@ -243,4 +243,69 @@ public void call() { ts.assertNoErrors(); ts.assertCompleted(); } + + @Test + public void unsubscriptionPropagatesBeforeSubscribe() { + PublishSubject source = PublishSubject.create(); + PublishSubject other = PublishSubject.create(); + + TestSubscriber ts = new TestSubscriber(); + + source.delaySubscription(other).subscribe(ts); + + Assert.assertFalse("source subscribed?", source.hasObservers()); + Assert.assertTrue("other not subscribed?", other.hasObservers()); + + ts.unsubscribe(); + + Assert.assertFalse("source subscribed?", source.hasObservers()); + Assert.assertFalse("other still subscribed?", other.hasObservers()); + } + + @Test + public void unsubscriptionPropagatesAfterSubscribe() { + PublishSubject source = PublishSubject.create(); + PublishSubject other = PublishSubject.create(); + + TestSubscriber ts = new TestSubscriber(); + + source.delaySubscription(other).subscribe(ts); + + Assert.assertFalse("source subscribed?", source.hasObservers()); + Assert.assertTrue("other not subscribed?", other.hasObservers()); + + other.onCompleted(); + + Assert.assertTrue("source not subscribed?", source.hasObservers()); + Assert.assertFalse("other still subscribed?", other.hasObservers()); + + ts.unsubscribe(); + + Assert.assertFalse("source subscribed?", source.hasObservers()); + Assert.assertFalse("other still subscribed?", other.hasObservers()); + } + + @Test + public void delayAndTakeUntilNeverSubscribeToSource() { + PublishSubject delayUntil = PublishSubject.create(); + PublishSubject interrupt = PublishSubject.create(); + final AtomicBoolean subscribed = new AtomicBoolean(false); + + Observable.just(1) + .doOnSubscribe(new Action0() { + @Override + public void call() { + subscribed.set(true); + } + }) + .delaySubscription(delayUntil) + .takeUntil(interrupt) + .subscribe(); + + interrupt.onNext(9000); + delayUntil.onNext(1); + + Assert.assertFalse(subscribed.get()); + } + } diff --git a/src/test/java/rx/internal/operators/OperatorDelayTest.java b/src/test/java/rx/internal/operators/OperatorDelayTest.java index e4db021eaf..315248a5db 100644 --- a/src/test/java/rx/internal/operators/OperatorDelayTest.java +++ b/src/test/java/rx/internal/operators/OperatorDelayTest.java @@ -30,9 +30,9 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; import org.mockito.InOrder; import org.mockito.Mock; @@ -41,9 +41,7 @@ import rx.Observer; import rx.Subscription; import rx.exceptions.TestException; -import rx.functions.Action1; -import rx.functions.Func0; -import rx.functions.Func1; +import rx.functions.*; import rx.internal.util.RxRingBuffer; import rx.observers.TestObserver; import rx.observers.TestSubscriber; @@ -821,4 +819,47 @@ public void testErrorRunsBeforeOnNext() { ts.assertError(TestException.class); ts.assertNotCompleted(); } + + @Test + public void delaySubscriptionCancelBeforeTime() { + PublishSubject source = PublishSubject.create(); + + TestSubscriber ts = new TestSubscriber(); + + source.delaySubscription(100, TimeUnit.MILLISECONDS, scheduler).subscribe(ts); + + Assert.assertFalse("source subscribed?", source.hasObservers()); + + ts.unsubscribe(); + + Assert.assertFalse("source subscribed?", source.hasObservers()); + + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + + Assert.assertFalse("source subscribed?", source.hasObservers()); + } + + @Test + public void delayAndTakeUntilNeverSubscribeToSource() { + PublishSubject interrupt = PublishSubject.create(); + final AtomicBoolean subscribed = new AtomicBoolean(false); + TestScheduler testScheduler = new TestScheduler(); + + Observable.just(1) + .doOnSubscribe(new Action0() { + @Override + public void call() { + subscribed.set(true); + } + }) + .delaySubscription(1, TimeUnit.SECONDS, testScheduler) + .takeUntil(interrupt) + .subscribe(); + + interrupt.onNext(9000); + testScheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + Assert.assertFalse(subscribed.get()); + } + } From 6abde0cc99035f079976ead0fc8f812e76f13edd Mon Sep 17 00:00:00 2001 From: akarnokd Date: Fri, 8 Apr 2016 23:56:43 +0200 Subject: [PATCH 021/294] 1.x: Release 1.1.3 CHANGES.md update --- CHANGES.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 1d6ec229ea..39da0ad41b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,43 @@ # RxJava Releases # +### Version 1.1.3 - April 8, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.1.3%7C)) + +#### API enhancements + + - [Pull 3780](https://github.com/ReactiveX/RxJava/pull/3780): `SyncOnSubscribe` has been promoted to `@Beta` API. + - [Pull 3799](https://github.com/ReactiveX/RxJava/pull/3799): Added `Completable.andThen(Single)` operator + - [Pull 3777](https://github.com/ReactiveX/RxJava/pull/3777): Added `observeOn` overload to configure the prefetch/buffer size + - [Pull 3790](https://github.com/ReactiveX/RxJava/pull/3790): Make `Single.lift` public and `@Experimental` + - [Pull 3818](https://github.com/ReactiveX/RxJava/pull/3818): `fromCallable` promotion to `@Beta` + - [Pull 3842](https://github.com/ReactiveX/RxJava/pull/3842): improve `ExecutorScheduler` worker unsubscription + +#### API deprecations + + - [Pull 3762](https://github.com/ReactiveX/RxJava/pull/3762): Deprecate `CompositeException` constructor with message prefix + +#### General enhancements + + - [Pull 3828](https://github.com/ReactiveX/RxJava/pull/3828): `AsyncSubject` now supports backpressure + - [Pull 3829](https://github.com/ReactiveX/RxJava/pull/3829): Added `rx.unsafe-disable` system property to disable use of `sun.misc.Unsafe` even if it is available + - [Pull 3757](https://github.com/ReactiveX/RxJava/pull/3757): **Warning: behavior change!** Operator `sample` emits last sampled value before termination + +#### Performance enhancements + + - [Pull 3795](https://github.com/ReactiveX/RxJava/pull/3795): `observeOn` now replenishes with constant rate + +#### Bugfixes + + - [Pull 3809](https://github.com/ReactiveX/RxJava/pull/3809): fix `merge`/`flatMap` crash when the inner source was `just(null)` + - [Pull 3789](https://github.com/ReactiveX/RxJava/pull/3789): Prevent `Single.zip()` of zero `Single`s + - [Pull 3787](https://github.com/ReactiveX/RxJava/pull/3787): fix `groupBy` delaying group completion till all groups were emitted + - [Pull 3823](https://github.com/ReactiveX/RxJava/pull/3823): fix `DoAfterTerminate` handle if action throws + - [Pull 3822](https://github.com/ReactiveX/RxJava/pull/3822): make defensive copy of the properties in `RxJavaPlugins` + - [Pull 3836](https://github.com/ReactiveX/RxJava/pull/3836): fix `switchMap`/`switchOnNext` producer retention and backpressure + - [Pull 3840](https://github.com/ReactiveX/RxJava/pull/3840): fix `concatMap` scalar/empty source behavior + - [Pull 3839](https://github.com/ReactiveX/RxJava/pull/3839): fix `takeLast()` backpressure + - [Pull 3845](https://github.com/ReactiveX/RxJava/pull/3845): fix delaySubscription(Observable) unsubscription before triggered + + ### Version 1.1.2 - March 18, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.1.2%7C)) #### API Enhancements From 3e148a8832c18b0ac3297da4620d2d8c8a78da40 Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Sun, 10 Apr 2016 17:52:59 -0400 Subject: [PATCH 022/294] Remove unused local. --- src/main/java/rx/subjects/ReplaySubject.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/rx/subjects/ReplaySubject.java b/src/main/java/rx/subjects/ReplaySubject.java index db8490f731..f1b693dc9a 100644 --- a/src/main/java/rx/subjects/ReplaySubject.java +++ b/src/main/java/rx/subjects/ReplaySubject.java @@ -717,8 +717,7 @@ public boolean isEmpty() { @SuppressWarnings("unchecked") public T[] toArray(T[] a) { List list = new ArrayList(); - NodeList.Node l = head(); - NodeList.Node next = l.next; + NodeList.Node next = head().next; while (next != null) { Object o = leaveTransform.call(next.value); @@ -727,7 +726,6 @@ public T[] toArray(T[] a) { } else { list.add((T)o); } - l = next; next = next.next; } return list.toArray(a); From d799ecac4cb496303f774b1864882dbcabbf7ec7 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Thu, 14 Apr 2016 19:50:26 +0200 Subject: [PATCH 023/294] 1.x: deanonymize Observable inner classes (#3848) * 1.x: deanonymize Observable inner classes * Further simplification of types * Fix indentation, rename Lambda to Action --- src/main/java/rx/Observable.java | 520 +++--------------- src/main/java/rx/functions/Actions.java | 23 + .../operators/EmptyObservableHolder.java | 46 ++ .../operators/NeverObservableHolder.java | 45 ++ .../internal/operators/OnSubscribeLift.java | 65 +++ .../internal/operators/OnSubscribeThrow.java | 46 ++ .../util/ActionNotificationObserver.java | 48 ++ .../rx/internal/util/ActionSubscriber.java | 51 ++ .../util/InternalObservableUtils.java | 379 +++++++++++++ .../rx/internal/util/ObserverSubscriber.java | 46 ++ 10 files changed, 825 insertions(+), 444 deletions(-) create mode 100644 src/main/java/rx/internal/operators/EmptyObservableHolder.java create mode 100644 src/main/java/rx/internal/operators/NeverObservableHolder.java create mode 100644 src/main/java/rx/internal/operators/OnSubscribeLift.java create mode 100644 src/main/java/rx/internal/operators/OnSubscribeThrow.java create mode 100644 src/main/java/rx/internal/util/ActionNotificationObserver.java create mode 100644 src/main/java/rx/internal/util/ActionSubscriber.java create mode 100644 src/main/java/rx/internal/util/InternalObservableUtils.java create mode 100644 src/main/java/rx/internal/util/ObserverSubscriber.java diff --git a/src/main/java/rx/Observable.java b/src/main/java/rx/Observable.java index bd96ee4556..92e9ea56fa 100644 --- a/src/main/java/rx/Observable.java +++ b/src/main/java/rx/Observable.java @@ -19,7 +19,6 @@ import rx.exceptions.*; import rx.functions.*; import rx.internal.operators.*; -import rx.internal.producers.SingleProducer; import rx.internal.util.*; import rx.observables.*; import rx.observers.SafeSubscriber; @@ -191,11 +190,23 @@ public interface Operator extends Func1, Subscriber< */ @Experimental public R extend(Func1, ? extends R> conversion) { - return conversion.call(new OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - subscriber.add(Observable.subscribe(subscriber, Observable.this)); - }}); + return conversion.call(new OnSubscribeExtend(this)); + } + + /** + * Transforms a OnSubscribe.call() into an Observable.subscribe() call. + *

Note: has to be in Observable because it calls the private subscribe() method + * @param the value type + */ + static final class OnSubscribeExtend implements OnSubscribe { + final Observable parent; + OnSubscribeExtend(Observable parent) { + this.parent = parent; + } + @Override + public void call(Subscriber subscriber) { + subscriber.add(subscribe(subscriber, parent)); + } } /** @@ -222,30 +233,7 @@ public void call(Subscriber subscriber) { * @see RxJava wiki: Implementing Your Own Operators */ public final Observable lift(final Operator operator) { - return new Observable(new OnSubscribe() { - @Override - public void call(Subscriber o) { - try { - Subscriber st = hook.onLift(operator).call(o); - try { - // new Subscriber created and being subscribed with so 'onStart' it - st.onStart(); - onSubscribe.call(st); - } catch (Throwable e) { - // localized capture of errors rather than it skipping all operators - // and ending up in the try/catch of the subscribe method which then - // prevents onErrorResumeNext and other similar approaches to error handling - Exceptions.throwIfFatal(e); - st.onError(e); - } - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - // if the lift function failed all we can do is pass the error to the final Subscriber - // as we don't have the operator available to us - o.onError(e); - } - } - }); + return new Observable(new OnSubscribeLift(onSubscribe, operator)); } /** @@ -1253,16 +1241,6 @@ public static Observable defer(Func0> observableFactory) { return create(new OnSubscribeDefer(observableFactory)); } - /** Lazy initialized Holder for an empty observable which just emits onCompleted to any subscriber. */ - private static final class EmptyHolder { - final static Observable INSTANCE = create(new OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - subscriber.onCompleted(); - } - }); - } - /** * Returns an Observable that emits no items to the {@link Observer} and immediately invokes its * {@link Observer#onCompleted onCompleted} method. @@ -1279,9 +1257,8 @@ public void call(Subscriber subscriber) { * {@link Observer}'s {@link Observer#onCompleted() onCompleted} method * @see ReactiveX operators documentation: Empty */ - @SuppressWarnings("unchecked") public static Observable empty() { - return (Observable) EmptyHolder.INSTANCE; + return EmptyObservableHolder.instance(); } /** @@ -1303,7 +1280,7 @@ public static Observable empty() { * @see ReactiveX operators documentation: Throw */ public static Observable error(Throwable exception) { - return new ThrowObservable(exception); + return create(new OnSubscribeThrow(exception)); } /** @@ -2740,7 +2717,7 @@ public final Observable> nest() { * @see ReactiveX operators documentation: Never */ public static Observable never() { - return NeverObservable.instance(); + return NeverObservableHolder.instance(); } /** @@ -2821,17 +2798,9 @@ public static Observable range(int start, int count, Scheduler schedule * @see ReactiveX operators documentation: SequenceEqual */ public static Observable sequenceEqual(Observable first, Observable second) { - return sequenceEqual(first, second, new Func2() { - @Override - public final Boolean call(T first, T second) { - if (first == null) { - return second == null; - } - return first.equals(second); - } - }); + return sequenceEqual(first, second, InternalObservableUtils.OBJECT_EQUALS); } - + /** * Returns an Observable that emits a Boolean value that indicates whether two Observable sequences are the * same by comparing the items emitted by each Observable pairwise based on the results of a specified @@ -3146,16 +3115,9 @@ public static Observable zip(Iterable> ws, FuncN< * @see ReactiveX operators documentation: Zip */ public static Observable zip(Observable> ws, final FuncN zipFunction) { - return ws.toList().map(new Func1>, Observable[]>() { - - @Override - public Observable[] call(List> o) { - return o.toArray(new Observable[o.size()]); - } - - }).lift(new OperatorZip(zipFunction)); + return ws.toList().map(InternalObservableUtils.TO_ARRAY).lift(new OperatorZip(zipFunction)); } - + /** * Returns an Observable that emits the results of a specified combiner function applied to combinations of * two items emitted, in sequence, by two other Observables. @@ -4016,15 +3978,7 @@ public final Observable cast(final Class klass) { * @see ReactiveX operators documentation: Reduce */ public final Observable collect(Func0 stateFactory, final Action2 collector) { - Func2 accumulator = new Func2() { - - @Override - public final R call(R state, T value) { - collector.call(state, value); - return state; - } - - }; + Func2 accumulator = InternalObservableUtils.createCollectorCaller(collector); /* * Discussion and confirmation of implementation at @@ -4147,12 +4101,7 @@ public final Observable concatWith(Observable t1) { * @see ReactiveX operators documentation: Contains */ public final Observable contains(final Object element) { - return exists(new Func1() { - @Override - public final Boolean call(T t1) { - return element == null ? t1 == null : element.equals(t1); - } - }); + return exists(InternalObservableUtils.equalsWith(element)); } /** @@ -4172,16 +4121,7 @@ public final Boolean call(T t1) { * @see #countLong() */ public final Observable count() { - return reduce(0, CountHolder.INSTANCE); - } - - private static final class CountHolder { - static final Func2 INSTANCE = new Func2() { - @Override - public final Integer call(Integer count, Object o) { - return count + 1; - } - }; + return reduce(0, InternalObservableUtils.COUNTER); } /** @@ -4203,18 +4143,9 @@ public final Integer call(Integer count, Object o) { * @see #count() */ public final Observable countLong() { - return reduce(0L, CountLongHolder.INSTANCE); + return reduce(0L, InternalObservableUtils.LONG_COUNTER); } - private static final class CountLongHolder { - static final Func2 INSTANCE = new Func2() { - @Override - public final Long call(Long count, Object o) { - return count + 1; - } - }; - } - /** * Returns an Observable that mirrors the source Observable, except that it drops items emitted by the * source Observable that are followed by another item within a computed debounce duration. @@ -4340,12 +4271,7 @@ public final Observable debounce(long timeout, TimeUnit unit, Scheduler sched */ public final Observable defaultIfEmpty(final T defaultValue) { //if empty switch to an observable that emits defaultValue and supports backpressure - return switchIfEmpty(Observable.create(new OnSubscribe() { - - @Override - public void call(Subscriber subscriber) { - subscriber.setProducer(new SingleProducer(subscriber, defaultValue)); - }})); + return switchIfEmpty(just(defaultValue)); } /** @@ -4676,21 +4602,9 @@ public final Observable distinctUntilChanged(Func1ReactiveX operators documentation: Do */ public final Observable doOnCompleted(final Action0 onCompleted) { - Observer observer = new Observer() { - @Override - public final void onCompleted() { - onCompleted.call(); - } - - @Override - public final void onError(Throwable e) { - } - - @Override - public final void onNext(T args) { - } - - }; + Action1 onNext = Actions.empty(); + Action1 onError = Actions.empty(); + Observer observer = new ActionSubscriber(onNext, onError, onCompleted); return lift(new OperatorDoOnEach(observer)); } @@ -4710,23 +4624,7 @@ public final void onNext(T args) { * @see ReactiveX operators documentation: Do */ public final Observable doOnEach(final Action1> onNotification) { - Observer observer = new Observer() { - @Override - public final void onCompleted() { - onNotification.call(Notification.createOnCompleted()); - } - - @Override - public final void onError(Throwable e) { - onNotification.call(Notification.createOnError(e)); - } - - @Override - public final void onNext(T v) { - onNotification.call(Notification.createOnNext(v)); - } - - }; + Observer observer = new ActionNotificationObserver(onNotification); return lift(new OperatorDoOnEach(observer)); } @@ -4772,21 +4670,9 @@ public final Observable doOnEach(Observer observer) { * @see ReactiveX operators documentation: Do */ public final Observable doOnError(final Action1 onError) { - Observer observer = new Observer() { - @Override - public final void onCompleted() { - } - - @Override - public final void onError(Throwable e) { - onError.call(e); - } - - @Override - public final void onNext(T args) { - } - - }; + Action1 onNext = Actions.empty(); + Action0 onCompleted = Actions.empty(); + Observer observer = new ActionSubscriber(onNext, onError, onCompleted); return lift(new OperatorDoOnEach(observer)); } @@ -4806,21 +4692,9 @@ public final void onNext(T args) { * @see ReactiveX operators documentation: Do */ public final Observable doOnNext(final Action1 onNext) { - Observer observer = new Observer() { - @Override - public final void onCompleted() { - } - - @Override - public final void onError(Throwable e) { - } - - @Override - public final void onNext(T args) { - onNext.call(args); - } - - }; + Action1 onError = Actions.empty(); + Action0 onCompleted = Actions.empty(); + Observer observer = new ActionSubscriber(onNext, onError, onCompleted); return lift(new OperatorDoOnEach(observer)); } @@ -4891,22 +4765,10 @@ public final Observable doOnSubscribe(final Action0 subscribe) { * @see #finallyDo(Action0) */ public final Observable doOnTerminate(final Action0 onTerminate) { - Observer observer = new Observer() { - @Override - public final void onCompleted() { - onTerminate.call(); - } - - @Override - public final void onError(Throwable e) { - onTerminate.call(); - } - - @Override - public final void onNext(T args) { - } - - }; + Action1 onNext = Actions.empty(); + Action1 onError = Actions.toAction1(onTerminate); + + Observer observer = new ActionSubscriber(onNext, onError, onTerminate); return lift(new OperatorDoOnEach(observer)); } @@ -6111,15 +5973,10 @@ public final Observable ignoreElements() { * @return an Observable that emits a Boolean * @see ReactiveX operators documentation: Contains */ - @SuppressWarnings("unchecked") public final Observable isEmpty() { - return lift((OperatorAny) HolderAnyForEmpty.INSTANCE); + return lift(InternalObservableUtils.IS_EMPTY); } - private static class HolderAnyForEmpty { - static final OperatorAny INSTANCE = new OperatorAny(UtilityFunctions.alwaysTrue(), true); - } - /** * Correlates the items emitted by two Observables based on overlapping durations. *

@@ -6459,12 +6316,7 @@ public final Observable observeOn(Scheduler scheduler, boolean delayError, in * @see ReactiveX operators documentation: Filter */ public final Observable ofType(final Class klass) { - return filter(new Func1() { - @Override - public final Boolean call(T t) { - return klass.isInstance(t); - } - }).cast(klass); + return filter(InternalObservableUtils.isInstanceOf(klass)).cast(klass); } /** @@ -6976,18 +6828,7 @@ public final Observable repeat(final long count, Scheduler scheduler) { * @see ReactiveX operators documentation: Repeat */ public final Observable repeatWhen(final Func1, ? extends Observable> notificationHandler, Scheduler scheduler) { - Func1>, ? extends Observable> dematerializedNotificationHandler = new Func1>, Observable>() { - @Override - public Observable call(Observable> notifications) { - return notificationHandler.call(notifications.map(new Func1, Void>() { - @Override - public Void call(Notification notification) { - return null; - } - })); - } - }; - return OnSubscribeRedo.repeat(this, dematerializedNotificationHandler, scheduler); + return OnSubscribeRedo.repeat(this, InternalObservableUtils.createRepeatDematerializer(notificationHandler), scheduler); } /** @@ -7010,18 +6851,7 @@ public Void call(Notification notification) { * @see ReactiveX operators documentation: Repeat */ public final Observable repeatWhen(final Func1, ? extends Observable> notificationHandler) { - Func1>, ? extends Observable> dematerializedNotificationHandler = new Func1>, Observable>() { - @Override - public Observable call(Observable> notifications) { - return notificationHandler.call(notifications.map(new Func1, Void>() { - @Override - public Void call(Notification notification) { - return null; - } - })); - } - }; - return OnSubscribeRedo.repeat(this, dematerializedNotificationHandler); + return OnSubscribeRedo.repeat(this, InternalObservableUtils.createRepeatDematerializer(notificationHandler)); } /** @@ -7072,12 +6902,7 @@ public final ConnectableObservable replay() { * @see ReactiveX operators documentation: Replay */ public final Observable replay(Func1, ? extends Observable> selector) { - return OperatorReplay.multicastSelector(new Func0>() { - @Override - public ConnectableObservable call() { - return Observable.this.replay(); - } - }, selector); + return OperatorReplay.multicastSelector(InternalObservableUtils.createReplaySupplier(this), selector); } /** @@ -7108,12 +6933,7 @@ public ConnectableObservable call() { * @see ReactiveX operators documentation: Replay */ public final Observable replay(Func1, ? extends Observable> selector, final int bufferSize) { - return OperatorReplay.multicastSelector(new Func0>() { - @Override - public ConnectableObservable call() { - return Observable.this.replay(bufferSize); - } - }, selector); + return OperatorReplay.multicastSelector(InternalObservableUtils.createReplaySupplier(this, bufferSize), selector); } /** @@ -7192,12 +7012,8 @@ public final Observable replay(Func1, ? extends Obs if (bufferSize < 0) { throw new IllegalArgumentException("bufferSize < 0"); } - return OperatorReplay.multicastSelector(new Func0>() { - @Override - public ConnectableObservable call() { - return Observable.this.replay(bufferSize, time, unit, scheduler); - } - }, selector); + return OperatorReplay.multicastSelector( + InternalObservableUtils.createReplaySupplier(this, bufferSize, time, unit, scheduler), selector); } /** @@ -7230,17 +7046,8 @@ public ConnectableObservable call() { * @see ReactiveX operators documentation: Replay */ public final Observable replay(final Func1, ? extends Observable> selector, final int bufferSize, final Scheduler scheduler) { - return OperatorReplay.multicastSelector(new Func0>() { - @Override - public ConnectableObservable call() { - return Observable.this.replay(bufferSize); - } - }, new Func1, Observable>() { - @Override - public Observable call(Observable t) { - return selector.call(t).observeOn(scheduler); - } - }); + return OperatorReplay.multicastSelector(InternalObservableUtils.createReplaySupplier(this, bufferSize), + InternalObservableUtils.createReplaySelectorAndObserveOn(selector, scheduler)); } /** @@ -7308,12 +7115,8 @@ public final Observable replay(Func1, ? extends Obs * @see ReactiveX operators documentation: Replay */ public final Observable replay(Func1, ? extends Observable> selector, final long time, final TimeUnit unit, final Scheduler scheduler) { - return OperatorReplay.multicastSelector(new Func0>() { - @Override - public ConnectableObservable call() { - return Observable.this.replay(time, unit, scheduler); - } - }, selector); + return OperatorReplay.multicastSelector( + InternalObservableUtils.createReplaySupplier(this, time, unit, scheduler), selector); } /** @@ -7343,17 +7146,9 @@ public ConnectableObservable call() { * @see ReactiveX operators documentation: Replay */ public final Observable replay(final Func1, ? extends Observable> selector, final Scheduler scheduler) { - return OperatorReplay.multicastSelector(new Func0>() { - @Override - public ConnectableObservable call() { - return Observable.this.replay(); - } - }, new Func1, Observable>() { - @Override - public Observable call(Observable t) { - return selector.call(t).observeOn(scheduler); - } - }); + return OperatorReplay.multicastSelector( + InternalObservableUtils.createReplaySupplier(this), + InternalObservableUtils.createReplaySelectorAndObserveOn(selector, scheduler)); } /** @@ -7689,18 +7484,7 @@ public final Observable retry(Func2 predicate) { * @see ReactiveX operators documentation: Retry */ public final Observable retryWhen(final Func1, ? extends Observable> notificationHandler) { - Func1>, ? extends Observable> dematerializedNotificationHandler = new Func1>, Observable>() { - @Override - public Observable call(Observable> notifications) { - return notificationHandler.call(notifications.map(new Func1, Throwable>() { - @Override - public Throwable call(Notification notification) { - return notification.getThrowable(); - } - })); - } - }; - return OnSubscribeRedo. retry(this, dematerializedNotificationHandler); + return OnSubscribeRedo.retry(this, InternalObservableUtils.createRetryDematerializer(notificationHandler)); } /** @@ -7727,18 +7511,7 @@ public Throwable call(Notification notification) { * @see ReactiveX operators documentation: Retry */ public final Observable retryWhen(final Func1, ? extends Observable> notificationHandler, Scheduler scheduler) { - Func1>, ? extends Observable> dematerializedNotificationHandler = new Func1>, Observable>() { - @Override - public Observable call(Observable> notifications) { - return notificationHandler.call(notifications.map(new Func1, Throwable>() { - @Override - public Throwable call(Notification notification) { - return notification.getThrowable(); - } - })); - } - }; - return OnSubscribeRedo. retry(this, dematerializedNotificationHandler, scheduler); + return OnSubscribeRedo. retry(this, InternalObservableUtils.createRetryDematerializer(notificationHandler), scheduler); } /** @@ -8512,24 +8285,10 @@ public final Observable startWith(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T * @see ReactiveX operators documentation: Subscribe */ public final Subscription subscribe() { - return subscribe(new Subscriber() { - - @Override - public final void onCompleted() { - // do nothing - } - - @Override - public final void onError(Throwable e) { - throw new OnErrorNotImplementedException(e); - } - - @Override - public final void onNext(T args) { - // do nothing - } - - }); + Action1 onNext = Actions.empty(); + Action1 onError = InternalObservableUtils.ERROR_NOT_IMPLEMENTED; + Action0 onCompleted = Actions.empty(); + return subscribe(new ActionSubscriber(onNext, onError, onCompleted)); } /** @@ -8554,24 +8313,9 @@ public final Subscription subscribe(final Action1 onNext) { throw new IllegalArgumentException("onNext can not be null"); } - return subscribe(new Subscriber() { - - @Override - public final void onCompleted() { - // do nothing - } - - @Override - public final void onError(Throwable e) { - throw new OnErrorNotImplementedException(e); - } - - @Override - public final void onNext(T args) { - onNext.call(args); - } - - }); + Action1 onError = InternalObservableUtils.ERROR_NOT_IMPLEMENTED; + Action0 onCompleted = Actions.empty(); + return subscribe(new ActionSubscriber(onNext, onError, onCompleted)); } /** @@ -8602,24 +8346,8 @@ public final Subscription subscribe(final Action1 onNext, final Actio throw new IllegalArgumentException("onError can not be null"); } - return subscribe(new Subscriber() { - - @Override - public final void onCompleted() { - // do nothing - } - - @Override - public final void onError(Throwable e) { - onError.call(e); - } - - @Override - public final void onNext(T args) { - onNext.call(args); - } - - }); + Action0 onCompleted = Actions.empty(); + return subscribe(new ActionSubscriber(onNext, onError, onCompleted)); } /** @@ -8635,7 +8363,7 @@ public final void onNext(T args) { * @param onError * the {@code Action1} you have designed to accept any error notification from the * Observable - * @param onComplete + * @param onCompleted * the {@code Action0} you have designed to accept a completion notification from the * Observable * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items before @@ -8646,35 +8374,18 @@ public final void onNext(T args) { * if {@code onComplete} is null * @see ReactiveX operators documentation: Subscribe */ - public final Subscription subscribe(final Action1 onNext, final Action1 onError, final Action0 onComplete) { + public final Subscription subscribe(final Action1 onNext, final Action1 onError, final Action0 onCompleted) { if (onNext == null) { throw new IllegalArgumentException("onNext can not be null"); } if (onError == null) { throw new IllegalArgumentException("onError can not be null"); } - if (onComplete == null) { + if (onCompleted == null) { throw new IllegalArgumentException("onComplete can not be null"); } - return subscribe(new Subscriber() { - - @Override - public final void onCompleted() { - onComplete.call(); - } - - @Override - public final void onError(Throwable e) { - onError.call(e); - } - - @Override - public final void onNext(T args) { - onNext.call(args); - } - - }); + return subscribe(new ActionSubscriber(onNext, onError, onCompleted)); } /** @@ -8695,24 +8406,7 @@ public final Subscription subscribe(final Observer observer) { if (observer instanceof Subscriber) { return subscribe((Subscriber)observer); } - return subscribe(new Subscriber() { - - @Override - public void onCompleted() { - observer.onCompleted(); - } - - @Override - public void onError(Throwable e) { - observer.onError(e); - } - - @Override - public void onNext(T t) { - observer.onNext(t); - } - - }); + return subscribe(new ObserverSubscriber(observer)); } /** @@ -8801,7 +8495,7 @@ public final Subscription subscribe(Subscriber subscriber) { return Observable.subscribe(subscriber, this); } - private static Subscription subscribe(Subscriber subscriber, Observable observable) { + static Subscription subscribe(Subscriber subscriber, Observable observable) { // validate and proceed if (subscriber == null) { throw new IllegalArgumentException("observer can not be null"); @@ -10601,66 +10295,4 @@ public final Observable zipWith(Iterable other, Func2 Observable zipWith(Observable other, Func2 zipFunction) { return (Observable)zip(this, other, zipFunction); } - - /** - * An Observable that never sends any information to an {@link Observer}. - * This Observable is useful primarily for testing purposes. - * - * @param - * the type of item (not) emitted by the Observable - */ - private static class NeverObservable extends Observable { - - private static class Holder { - static final NeverObservable INSTANCE = new NeverObservable(); - } - - /** - * Returns a singleton instance of NeverObservable (cast to the generic type). - * - * @return singleton instance of NeverObservable (cast to the generic type) - */ - @SuppressWarnings("unchecked") - static NeverObservable instance() { - return (NeverObservable) Holder.INSTANCE; - } - - NeverObservable() { - super(new OnSubscribe() { - - @Override - public void call(Subscriber observer) { - // do nothing - } - - }); - } - } - - /** - * An Observable that invokes {@link Observer#onError onError} when the {@link Observer} subscribes to it. - * - * @param - * the type of item (ostensibly) emitted by the Observable - */ - private static class ThrowObservable extends Observable { - - public ThrowObservable(final Throwable exception) { - super(new OnSubscribe() { - - /** - * Accepts an {@link Observer} and calls its {@link Observer#onError onError} method. - * - * @param observer - * an {@link Observer} of this Observable - */ - @Override - public void call(Subscriber observer) { - observer.onError(exception); - } - - }); - } - } - } diff --git a/src/main/java/rx/functions/Actions.java b/src/main/java/rx/functions/Actions.java index ea18eaed91..bbf9d0b151 100644 --- a/src/main/java/rx/functions/Actions.java +++ b/src/main/java/rx/functions/Actions.java @@ -432,4 +432,27 @@ public R call(Object... args) { } }; } + + /** + * Wraps an Action0 instance into an Action1 instance where the latter calls + * the former. + * @param action the action to call + * @return the new Action1 instance + */ + public static Action1 toAction1(Action0 action) { + return new Action1CallsAction0(action); + } + + static final class Action1CallsAction0 implements Action1 { + final Action0 action; + + public Action1CallsAction0(Action0 action) { + this.action = action; + } + + @Override + public void call(T t) { + action.call(); + } + } } diff --git a/src/main/java/rx/internal/operators/EmptyObservableHolder.java b/src/main/java/rx/internal/operators/EmptyObservableHolder.java new file mode 100644 index 0000000000..20091ca211 --- /dev/null +++ b/src/main/java/rx/internal/operators/EmptyObservableHolder.java @@ -0,0 +1,46 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.operators; + +import rx.*; +import rx.Observable.OnSubscribe; + +/** + * Holds a singleton instance of an empty Observable which is stateless and completes + * the child subscriber immediately. + */ +public enum EmptyObservableHolder implements OnSubscribe { + INSTANCE + ; + + /** + * Returns a type-corrected singleton instance of the empty Observable. + * @return a type-corrected singleton instance of the empty Observable. + */ + @SuppressWarnings("unchecked") + public static Observable instance() { + return (Observable)EMPTY; + } + + /** The singleton instance. */ + static final Observable EMPTY = Observable.create(INSTANCE); + + @Override + public void call(Subscriber child) { + child.onCompleted(); + } +} diff --git a/src/main/java/rx/internal/operators/NeverObservableHolder.java b/src/main/java/rx/internal/operators/NeverObservableHolder.java new file mode 100644 index 0000000000..451f31c67b --- /dev/null +++ b/src/main/java/rx/internal/operators/NeverObservableHolder.java @@ -0,0 +1,45 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.operators; + +import rx.*; +import rx.Observable.OnSubscribe; + +/** + * Holds a singleton instance of a never Observable which is stateless doesn't + * call any of the Subscriber's methods. + */ +public enum NeverObservableHolder implements OnSubscribe { + INSTANCE + ; + + /** + * Returns a type-corrected singleton instance of the never Observable. + * @return a type-corrected singleton instance of the never Observable. + */ + @SuppressWarnings("unchecked") + public static Observable instance() { + return (Observable)NEVER; + } + + /** The singleton instance. */ + static final Observable NEVER = Observable.create(INSTANCE); + + @Override + public void call(Subscriber child) { + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeLift.java b/src/main/java/rx/internal/operators/OnSubscribeLift.java new file mode 100644 index 0000000000..ba6210f38b --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeLift.java @@ -0,0 +1,65 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.operators; + +import rx.Observable.*; +import rx.Subscriber; +import rx.exceptions.Exceptions; +import rx.plugins.*; + +/** + * Transforms the downstream Subscriber into a Subscriber via an operator + * callback and calls the parent OnSubscribe.call() method with it. + * @param the source value type + * @param the result value type + */ +public final class OnSubscribeLift implements OnSubscribe { + + static final RxJavaObservableExecutionHook hook = RxJavaPlugins.getInstance().getObservableExecutionHook(); + + final OnSubscribe parent; + + final Operator operator; + + public OnSubscribeLift(OnSubscribe parent, Operator operator) { + this.parent = parent; + this.operator = operator; + } + + @Override + public void call(Subscriber o) { + try { + Subscriber st = hook.onLift(operator).call(o); + try { + // new Subscriber created and being subscribed with so 'onStart' it + st.onStart(); + parent.call(st); + } catch (Throwable e) { + // localized capture of errors rather than it skipping all operators + // and ending up in the try/catch of the subscribe method which then + // prevents onErrorResumeNext and other similar approaches to error handling + Exceptions.throwIfFatal(e); + st.onError(e); + } + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // if the lift function failed all we can do is pass the error to the final Subscriber + // as we don't have the operator available to us + o.onError(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OnSubscribeThrow.java b/src/main/java/rx/internal/operators/OnSubscribeThrow.java new file mode 100644 index 0000000000..d39bba5939 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeThrow.java @@ -0,0 +1,46 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.operators; + +import rx.*; +import rx.Observable.OnSubscribe; + +/** + * An Observable that invokes {@link Observer#onError onError} when the {@link Observer} subscribes to it. + * + * @param + * the type of item (ostensibly) emitted by the Observable + */ +public final class OnSubscribeThrow implements OnSubscribe { + + private final Throwable exception; + + public OnSubscribeThrow(Throwable exception) { + this.exception = exception; + } + + /** + * Accepts an {@link Observer} and calls its {@link Observer#onError onError} method. + * + * @param observer + * an {@link Observer} of this Observable + */ + @Override + public void call(Subscriber observer) { + observer.onError(exception); + } +} diff --git a/src/main/java/rx/internal/util/ActionNotificationObserver.java b/src/main/java/rx/internal/util/ActionNotificationObserver.java new file mode 100644 index 0000000000..162d9371b4 --- /dev/null +++ b/src/main/java/rx/internal/util/ActionNotificationObserver.java @@ -0,0 +1,48 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.internal.util; + +import rx.*; +import rx.functions.*; + +/** + * An Observer that forwards the onXXX method calls to a notification callback + * by transforming each signal type into Notifications. + * @param the value type + */ +public final class ActionNotificationObserver implements Observer { + + final Action1> onNotification; + + public ActionNotificationObserver(Action1> onNotification) { + this.onNotification = onNotification; + } + + @Override + public void onNext(T t) { + onNotification.call(Notification.createOnNext(t)); + } + + @Override + public void onError(Throwable e) { + onNotification.call(Notification.createOnError(e)); + } + + @Override + public void onCompleted() { + onNotification.call(Notification.createOnCompleted()); + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/util/ActionSubscriber.java b/src/main/java/rx/internal/util/ActionSubscriber.java new file mode 100644 index 0000000000..33a88fe5ed --- /dev/null +++ b/src/main/java/rx/internal/util/ActionSubscriber.java @@ -0,0 +1,51 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.internal.util; + +import rx.Subscriber; +import rx.functions.*; + +/** + * A Subscriber that forwards the onXXX method calls to callbacks. + * @param the value type + */ +public final class ActionSubscriber extends Subscriber { + + final Action1 onNext; + final Action1 onError; + final Action0 onCompleted; + + public ActionSubscriber(Action1 onNext, Action1 onError, Action0 onCompleted) { + this.onNext = onNext; + this.onError = onError; + this.onCompleted = onCompleted; + } + + @Override + public void onNext(T t) { + onNext.call(t); + } + + @Override + public void onError(Throwable e) { + onError.call(e); + } + + @Override + public void onCompleted() { + onCompleted.call(); + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/util/InternalObservableUtils.java b/src/main/java/rx/internal/util/InternalObservableUtils.java new file mode 100644 index 0000000000..0e8e3d6878 --- /dev/null +++ b/src/main/java/rx/internal/util/InternalObservableUtils.java @@ -0,0 +1,379 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.util; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import rx.*; +import rx.Observable.Operator; +import rx.exceptions.OnErrorNotImplementedException; +import rx.functions.*; +import rx.internal.operators.OperatorAny; +import rx.observables.ConnectableObservable; + +/** + * Holder of named utility classes factored out from Observable to save + * source space and help with debugging with properly named objects. + */ +public enum InternalObservableUtils { + ; + + /** + * A BiFunction that expects an integer as its first parameter and returns +1. + */ + public static final PlusOneFunc2 COUNTER = new PlusOneFunc2(); + + static final class PlusOneFunc2 implements Func2 { + @Override + public Integer call(Integer count, Object o) { + return count + 1; + } + } + + /** + * A BiFunction that expects a long as its first parameter and returns +1. + */ + public static final PlusOneLongFunc2 LONG_COUNTER = new PlusOneLongFunc2(); + + static final class PlusOneLongFunc2 implements Func2 { + @Override + public Long call(Long count, Object o) { + return count + 1; + } + } + + /** + * A bifunction comparing two objects via null-safe equals. + */ + public static final ObjectEqualsFunc2 OBJECT_EQUALS = new ObjectEqualsFunc2(); + + static final class ObjectEqualsFunc2 implements Func2 { + @Override + public Boolean call(Object first, Object second) { + return first == second || (first != null && first.equals(second)); + } + } + + /** + * A function that converts a List of Observables into an array of Observables. + */ + public static final ToArrayFunc1 TO_ARRAY = new ToArrayFunc1(); + + static final class ToArrayFunc1 implements Func1>, Observable[]> { + @Override + public Observable[] call(List> o) { + return o.toArray(new Observable[o.size()]); + } + } + + /** + * Returns a Func1 that checks if its argument is null-safe equals with the given + * constant reference. + * @param other the other object to check against (nulls allowed) + * @return the comparison function + */ + public static Func1 equalsWith(Object other) { + return new EqualsWithFunc1(other); + } + + static final class EqualsWithFunc1 implements Func1 { + final Object other; + + public EqualsWithFunc1(Object other) { + this.other = other; + } + + @Override + public Boolean call(Object t) { + return t == other || (t != null && t.equals(other)); + } + } + + /** + * Returns a Func1 that checks if its argument is an instance of + * the supplied class. + * @param clazz the class to check against + * @return the comparison function + */ + public static Func1 isInstanceOf(Class clazz) { + return new IsInstanceOfFunc1(clazz); + } + + static final class IsInstanceOfFunc1 implements Func1 { + final Class clazz; + + public IsInstanceOfFunc1(Class other) { + this.clazz = other; + } + + @Override + public Boolean call(Object t) { + return clazz.isInstance(t); + } + } + + /** + * Returns a function that dematerializes the notification signal from an Observable and calls + * a notification handler with a null for non-terminal events. + * @param notificationHandler the handler to notify with nulls + * @return the Func1 instance + */ + public static final Func1>, Observable> createRepeatDematerializer(Func1, ? extends Observable> notificationHandler) { + return new RepeatNotificationDematerializer(notificationHandler); + } + + static final class RepeatNotificationDematerializer implements Func1>, Observable> { + + final Func1, ? extends Observable> notificationHandler; + + public RepeatNotificationDematerializer(Func1, ? extends Observable> notificationHandler) { + this.notificationHandler = notificationHandler; + } + + @Override + public Observable call(Observable> notifications) { + return notificationHandler.call(notifications.map(RETURNS_VOID)); + } + }; + + static final ReturnsVoidFunc1 RETURNS_VOID = new ReturnsVoidFunc1(); + + static final class ReturnsVoidFunc1 implements Func1 { + @Override + public Void call(Object t) { + return null; + } + } + + /** + * Creates a Func1 which calls the selector function with the received argument, applies an + * observeOn on the result and returns the resulting Observable. + * @param selector the selector function + * @param scheduler the scheduler to apply on the output of the selector + * @return the new Func1 instance + */ + public static Func1, Observable> createReplaySelectorAndObserveOn( + Func1, ? extends Observable> selector, + Scheduler scheduler) { + return new SelectorAndObserveOn(selector, scheduler); + } + + static final class SelectorAndObserveOn implements Func1, Observable> { + final Func1, ? extends Observable> selector; + final Scheduler scheduler; + + public SelectorAndObserveOn(Func1, ? extends Observable> selector, + Scheduler scheduler) { + super(); + this.selector = selector; + this.scheduler = scheduler; + } + + + + @Override + public Observable call(Observable t) { + return selector.call(t).observeOn(scheduler); + } + } + + /** + * Returns a function that dematerializes the notification signal from an Observable and calls + * a notification handler with the Throwable. + * @param notificationHandler the handler to notify with Throwables + * @return the Func1 instance + */ + public static final Func1>, Observable> createRetryDematerializer(Func1, ? extends Observable> notificationHandler) { + return new RetryNotificationDematerializer(notificationHandler); + } + + static final class RetryNotificationDematerializer implements Func1>, Observable> { + final Func1, ? extends Observable> notificationHandler; + + public RetryNotificationDematerializer(Func1, ? extends Observable> notificationHandler) { + this.notificationHandler = notificationHandler; + } + + @Override + public Observable call(Observable> notifications) { + return notificationHandler.call(notifications.map(ERROR_EXTRACTOR)); + } + } + + static final NotificationErrorExtractor ERROR_EXTRACTOR = new NotificationErrorExtractor(); + + static final class NotificationErrorExtractor implements Func1, Throwable> { + @Override + public Throwable call(Notification t) { + return t.getThrowable(); + } + } + + /** + * Returns a Func0 that supplies the ConnectableObservable returned by calling replay() on the source. + * @param source the source to call replay on by the supplier function + * @return the new Func0 instance + */ + public static Func0> createReplaySupplier(final Observable source) { + return new ReplaySupplierNoParams(source); + } + + private static final class ReplaySupplierNoParams implements Func0> { + private final Observable source; + + private ReplaySupplierNoParams(Observable source) { + this.source = source; + } + + @Override + public ConnectableObservable call() { + return source.replay(); + } + } + /** + * Returns a Func0 that supplies the ConnectableObservable returned by calling a parameterized replay() on the source. + * @param source the source to call replay on by the supplier function + * @param bufferSize + * the buffer size that limits the number of items the connectable observable can replay + * @return the new Func0 instance + */ + public static Func0> createReplaySupplier(final Observable source, final int bufferSize) { + return new ReplaySupplierBuffer(source, bufferSize); + } + + static final class ReplaySupplierBuffer implements Func0> { + private final Observable source; + private final int bufferSize; + + private ReplaySupplierBuffer(Observable source, int bufferSize) { + this.source = source; + this.bufferSize = bufferSize; + } + + @Override + public ConnectableObservable call() { + return source.replay(bufferSize); + } + } + + /** + * Returns a Func0 that supplies the ConnectableObservable returned by calling a parameterized replay() on the source. + * @param source the source to call replay on by the supplier function + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return the new Func0 instance + */ + public static Func0> createReplaySupplier(final Observable source, final long time, final TimeUnit unit, final Scheduler scheduler) { + return new ReplaySupplierBufferTime(source, time, unit, scheduler); + } + + static final class ReplaySupplierBufferTime implements Func0> { + private final TimeUnit unit; + private final Observable source; + private final long time; + private final Scheduler scheduler; + + private ReplaySupplierBufferTime(Observable source, long time, TimeUnit unit, Scheduler scheduler) { + this.unit = unit; + this.source = source; + this.time = time; + this.scheduler = scheduler; + } + + @Override + public ConnectableObservable call() { + return source.replay(time, unit, scheduler); + } + } + + /** + * Returns a Func0 that supplies the ConnectableObservable returned by calling a parameterized replay() on the source. + * @param source the source to call replay on by the supplier function + * @param bufferSize + * the buffer size that limits the number of items the connectable observable can replay + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return the new Func0 instance + */ + public static Func0> createReplaySupplier(final Observable source, final int bufferSize, final long time, final TimeUnit unit, final Scheduler scheduler) { + return new ReplaySupplierTime(source, bufferSize, time, unit, scheduler); + } + + static final class ReplaySupplierTime implements Func0> { + private final long time; + private final TimeUnit unit; + private final Scheduler scheduler; + private final int bufferSize; + private final Observable source; + + private ReplaySupplierTime(Observable source, int bufferSize, long time, TimeUnit unit, + Scheduler scheduler) { + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + this.bufferSize = bufferSize; + this.source = source; + } + + @Override + public ConnectableObservable call() { + return source.replay(bufferSize, time, unit, scheduler); + } + } + + /** + * Returns a Func2 which calls a collector with its parameters and returns the first (R) parameter. + * @param collector the collector action to call + * @return the new Func2 instance + */ + public static Func2 createCollectorCaller(Action2 collector) { + return new CollectorCaller(collector); + } + + static final class CollectorCaller implements Func2 { + final Action2 collector; + + public CollectorCaller(Action2 collector) { + this.collector = collector; + } + + @Override + public R call(R state, T value) { + collector.call(state, value); + return state; + } + } + + /** + * Throws an OnErrorNotImplementedException when called. + */ + public static final Action1 ERROR_NOT_IMPLEMENTED = new ErrorNotImplementedAction(); + + static final class ErrorNotImplementedAction implements Action1 { + @Override + public void call(Throwable t) { + throw new OnErrorNotImplementedException(t); + } + } + + public static final Operator IS_EMPTY = new OperatorAny(UtilityFunctions.alwaysTrue(), true); +} diff --git a/src/main/java/rx/internal/util/ObserverSubscriber.java b/src/main/java/rx/internal/util/ObserverSubscriber.java new file mode 100644 index 0000000000..dbf519c263 --- /dev/null +++ b/src/main/java/rx/internal/util/ObserverSubscriber.java @@ -0,0 +1,46 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.util; + +import rx.*; + +/** + * Wraps an Observer and forwards the onXXX method calls to it. + * @param the value type + */ +public final class ObserverSubscriber extends Subscriber { + final Observer observer; + + public ObserverSubscriber(Observer observer) { + this.observer = observer; + } + + @Override + public void onNext(T t) { + observer.onNext(t); + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onCompleted() { + observer.onCompleted(); + } +} From 54c6ed2f3fe4fabb00baca3df0f97901dee5b587 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Thu, 14 Apr 2016 19:51:04 +0200 Subject: [PATCH 024/294] 1.x: ConcatMap vs ConcatMapIterable perf (#3853) --- .../rx/operators/ConcatMapInterablePerf.java | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 src/perf/java/rx/operators/ConcatMapInterablePerf.java diff --git a/src/perf/java/rx/operators/ConcatMapInterablePerf.java b/src/perf/java/rx/operators/ConcatMapInterablePerf.java new file mode 100644 index 0000000000..40ea778acb --- /dev/null +++ b/src/perf/java/rx/operators/ConcatMapInterablePerf.java @@ -0,0 +1,181 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.operators; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.infra.Blackhole; + +import rx.Observable; +import rx.functions.Func1; +import rx.jmh.LatchedObserver; + +/** + * Benchmark ConcatMapIterable. + *

+ * gradlew benchmarks "-Pjmh=-f 1 -tu s -bm thrpt -wi 5 -i 5 -r 1 .*ConcatMapInterablePerf.*" + *

+ * gradlew benchmarks "-Pjmh=-f 1 -tu ns -bm avgt -wi 5 -i 5 -r 1 .*ConcatMapInterablePerf.*" + */ +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +@State(Scope.Thread) +public class ConcatMapInterablePerf { + + @Param({"1", "10", "100", "1000", "10000", "100000", "1000000"}) + public int count; + + Observable justPlain; + + Observable justIterable; + + Observable rangePlain; + + Observable rangeIterable; + + Observable xrangePlain; + + Observable xrangeIterable; + + Observable chainPlain; + + Observable chainIterable; + + @Setup + public void setup() { + Integer[] values = new Integer[count]; + for (int i = 0; i < count; i++) { + values[i] = i; + } + + int c = 1000000 / count; + Integer[] xvalues = new Integer[c]; + for (int i = 0; i < c; i++) { + xvalues[i] = i; + } + + Observable source = Observable.from(values); + + justPlain = source.concatMap(new Func1>() { + @Override + public Observable call(Integer v) { + return Observable.just(v); + } + }); + justIterable = source.concatMapIterable(new Func1>() { + @Override + public Iterable call(Integer v) { + return Collections.singleton(v); + } + }); + + final Observable range = Observable.range(1, 2); + final List xrange = Arrays.asList(1, 2); + + rangePlain = source.concatMap(new Func1>() { + @Override + public Observable call(Integer v) { + return range; + } + }); + rangeIterable = source.concatMapIterable(new Func1>() { + @Override + public Iterable call(Integer v) { + return xrange; + } + }); + + final Observable xsource = Observable.from(xvalues); + final List xvaluesList = Arrays.asList(xvalues); + + xrangePlain = source.concatMap(new Func1>() { + @Override + public Observable call(Integer v) { + return xsource; + } + }); + xrangeIterable = source.concatMapIterable(new Func1>() { + @Override + public Iterable call(Integer v) { + return xvaluesList; + } + }); + + chainPlain = xrangePlain.concatMap(new Func1>() { + @Override + public Observable call(Integer v) { + return Observable.just(v); + } + }); + chainIterable = xrangeIterable.concatMapIterable(new Func1>() { + @Override + public Iterable call(Integer v) { + return Collections.singleton(v); + } + }); + } + + @Benchmark + public void justPlain(Blackhole bh) { + justPlain.subscribe(new LatchedObserver(bh)); + } + + @Benchmark + public void justIterable(Blackhole bh) { + justIterable.subscribe(new LatchedObserver(bh)); + } + + @Benchmark + public void rangePlain(Blackhole bh) { + rangePlain.subscribe(new LatchedObserver(bh)); + } + + @Benchmark + public void rangeIterable(Blackhole bh) { + rangeIterable.subscribe(new LatchedObserver(bh)); + } + + @Benchmark + public void xrangePlain(Blackhole bh) { + xrangePlain.subscribe(new LatchedObserver(bh)); + } + + @Benchmark + public void xrangeIterable(Blackhole bh) { + xrangeIterable.subscribe(new LatchedObserver(bh)); + } + + @Benchmark + public void chainPlain(Blackhole bh) { + chainPlain.subscribe(new LatchedObserver(bh)); + } + + @Benchmark + public void chainIterable(Blackhole bh) { + chainIterable.subscribe(new LatchedObserver(bh)); + } + +} From a4298155f8bb1a5677495941a5d0f3ab6cbbbf77 Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Mon, 18 Apr 2016 06:34:36 -0400 Subject: [PATCH 025/294] Provide factories for creating the default scheduler instances. (#3856) --- .../schedulers/CachedThreadScheduler.java | 7 ++-- .../schedulers/NewThreadScheduler.java | 36 +++++++++++++++++++ .../java/rx/plugins/RxJavaSchedulersHook.java | 29 +++++++++++++++ .../rx/schedulers/NewThreadScheduler.java | 18 +++------- src/main/java/rx/schedulers/Schedulers.java | 15 ++++---- 5 files changed, 81 insertions(+), 24 deletions(-) rename src/main/java/rx/{ => internal}/schedulers/CachedThreadScheduler.java (98%) create mode 100644 src/main/java/rx/internal/schedulers/NewThreadScheduler.java diff --git a/src/main/java/rx/schedulers/CachedThreadScheduler.java b/src/main/java/rx/internal/schedulers/CachedThreadScheduler.java similarity index 98% rename from src/main/java/rx/schedulers/CachedThreadScheduler.java rename to src/main/java/rx/internal/schedulers/CachedThreadScheduler.java index 31c6f9288f..8e131e58e5 100644 --- a/src/main/java/rx/schedulers/CachedThreadScheduler.java +++ b/src/main/java/rx/internal/schedulers/CachedThreadScheduler.java @@ -13,18 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package rx.schedulers; +package rx.internal.schedulers; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import rx.*; import rx.functions.Action0; -import rx.internal.schedulers.*; import rx.internal.util.RxThreadFactory; import rx.subscriptions.*; -/* package */final class CachedThreadScheduler extends Scheduler implements SchedulerLifecycle { +public final class CachedThreadScheduler extends Scheduler implements SchedulerLifecycle { private static final String WORKER_THREAD_NAME_PREFIX = "RxCachedThreadScheduler-"; static final RxThreadFactory WORKER_THREAD_FACTORY = new RxThreadFactory(WORKER_THREAD_NAME_PREFIX); @@ -234,4 +233,4 @@ public void setExpirationTime(long expirationTime) { this.expirationTime = expirationTime; } } -} \ No newline at end of file +} diff --git a/src/main/java/rx/internal/schedulers/NewThreadScheduler.java b/src/main/java/rx/internal/schedulers/NewThreadScheduler.java new file mode 100644 index 0000000000..19aaedf8db --- /dev/null +++ b/src/main/java/rx/internal/schedulers/NewThreadScheduler.java @@ -0,0 +1,36 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.internal.schedulers; + +import rx.Scheduler; +import rx.internal.util.RxThreadFactory; + +/** + * Schedules work on a new thread. + */ +public final class NewThreadScheduler extends Scheduler { + + private static final String THREAD_NAME_PREFIX = "RxNewThreadScheduler-"; + private static final RxThreadFactory THREAD_FACTORY = new RxThreadFactory(THREAD_NAME_PREFIX); + + public NewThreadScheduler() { + } + + @Override + public Worker createWorker() { + return new NewThreadWorker(THREAD_FACTORY); + } +} diff --git a/src/main/java/rx/plugins/RxJavaSchedulersHook.java b/src/main/java/rx/plugins/RxJavaSchedulersHook.java index 133cdc363a..38d09dede9 100644 --- a/src/main/java/rx/plugins/RxJavaSchedulersHook.java +++ b/src/main/java/rx/plugins/RxJavaSchedulersHook.java @@ -17,7 +17,12 @@ package rx.plugins; import rx.Scheduler; +import rx.annotations.Experimental; import rx.functions.Action0; +import rx.internal.schedulers.CachedThreadScheduler; +import rx.internal.schedulers.EventLoopsScheduler; +import rx.internal.schedulers.NewThreadScheduler; +import rx.schedulers.Schedulers; /** * This plugin class provides 2 ways to customize {@link Scheduler} functionality @@ -35,6 +40,30 @@ */ public class RxJavaSchedulersHook { + /** + * Create an instance of the default {@link Scheduler} used for {@link Schedulers#computation()}. + */ + @Experimental + public static Scheduler createComputationScheduler() { + return new EventLoopsScheduler(); + } + + /** + * Create an instance of the default {@link Scheduler} used for {@link Schedulers#io()}. + */ + @Experimental + public static Scheduler createIoScheduler() { + return new CachedThreadScheduler(); + } + + /** + * Create an instance of the default {@link Scheduler} used for {@link Schedulers#newThread()}. + */ + @Experimental + public static Scheduler createNewThreadScheduler() { + return new NewThreadScheduler(); + } + protected RxJavaSchedulersHook() { } diff --git a/src/main/java/rx/schedulers/NewThreadScheduler.java b/src/main/java/rx/schedulers/NewThreadScheduler.java index d8c87bd245..5dc6046268 100644 --- a/src/main/java/rx/schedulers/NewThreadScheduler.java +++ b/src/main/java/rx/schedulers/NewThreadScheduler.java @@ -16,28 +16,18 @@ package rx.schedulers; import rx.Scheduler; -import rx.internal.schedulers.NewThreadWorker; -import rx.internal.util.RxThreadFactory; /** - * Schedules work on a new thread. + * @deprecated This type was never publicly instantiable. Use {@link Schedulers#newThread()}. */ +@Deprecated public final class NewThreadScheduler extends Scheduler { - - private static final String THREAD_NAME_PREFIX = "RxNewThreadScheduler-"; - private static final RxThreadFactory THREAD_FACTORY = new RxThreadFactory(THREAD_NAME_PREFIX); - private static final NewThreadScheduler INSTANCE = new NewThreadScheduler(); - - /* package */static NewThreadScheduler instance() { - return INSTANCE; - } - private NewThreadScheduler() { - + throw new AssertionError(); } @Override public Worker createWorker() { - return new NewThreadWorker(THREAD_FACTORY); + return null; } } diff --git a/src/main/java/rx/schedulers/Schedulers.java b/src/main/java/rx/schedulers/Schedulers.java index 0ec2d3a273..f81235347c 100644 --- a/src/main/java/rx/schedulers/Schedulers.java +++ b/src/main/java/rx/schedulers/Schedulers.java @@ -19,6 +19,7 @@ import rx.internal.schedulers.*; import rx.internal.util.RxRingBuffer; import rx.plugins.RxJavaPlugins; +import rx.plugins.RxJavaSchedulersHook; import java.util.concurrent.Executor; @@ -34,25 +35,27 @@ public final class Schedulers { private static final Schedulers INSTANCE = new Schedulers(); private Schedulers() { - Scheduler c = RxJavaPlugins.getInstance().getSchedulersHook().getComputationScheduler(); + RxJavaSchedulersHook hook = RxJavaPlugins.getInstance().getSchedulersHook(); + + Scheduler c = hook.getComputationScheduler(); if (c != null) { computationScheduler = c; } else { - computationScheduler = new EventLoopsScheduler(); + computationScheduler = RxJavaSchedulersHook.createComputationScheduler(); } - Scheduler io = RxJavaPlugins.getInstance().getSchedulersHook().getIOScheduler(); + Scheduler io = hook.getIOScheduler(); if (io != null) { ioScheduler = io; } else { - ioScheduler = new CachedThreadScheduler(); + ioScheduler = RxJavaSchedulersHook.createIoScheduler(); } - Scheduler nt = RxJavaPlugins.getInstance().getSchedulersHook().getNewThreadScheduler(); + Scheduler nt = hook.getNewThreadScheduler(); if (nt != null) { newThreadScheduler = nt; } else { - newThreadScheduler = NewThreadScheduler.instance(); + newThreadScheduler = RxJavaSchedulersHook.createNewThreadScheduler(); } } From 6efc2cfa863ddf45f336249bf554d88ed8fa7f2d Mon Sep 17 00:00:00 2001 From: Artem Zinnatullin Date: Tue, 19 Apr 2016 09:18:23 +0300 Subject: [PATCH 026/294] 1.x: Add Single.toCompletable() (#3866) Closes #3865. --- src/main/java/rx/Single.java | 25 +++++++++++++++++++++++++ src/test/java/rx/SingleTest.java | 23 +++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/main/java/rx/Single.java b/src/main/java/rx/Single.java index b1e2ed4074..e954612b4f 100644 --- a/src/main/java/rx/Single.java +++ b/src/main/java/rx/Single.java @@ -2095,6 +2095,31 @@ public final Observable toObservable() { return asObservable(this); } + /** + * Returns a {@link Completable} that discards result of the {@link Single} (similar to + * {@link Observable#ignoreElements()}) and calls {@code onCompleted} when this source {@link Single} calls + * {@code onSuccess}. Error terminal event is propagated. + *

+ * + *

+ *
Scheduler:
+ *
{@code toCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Completable} that calls {@code onCompleted} on it's subscriber when the source {@link Single} + * calls {@code onSuccess}. + * @see ReactiveX documentation: + * Completable. + * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical + * with the release number). + */ + @Experimental + public final Completable toCompletable() { + return Completable.fromSingle(this); + } + /** * Returns a Single that mirrors the source Single but applies a timeout policy for its emitted item. If it * is not emitted within the specified timeout duration, the resulting Single terminates and notifies diff --git a/src/test/java/rx/SingleTest.java b/src/test/java/rx/SingleTest.java index 2952e22bfd..3760330eb4 100644 --- a/src/test/java/rx/SingleTest.java +++ b/src/test/java/rx/SingleTest.java @@ -812,6 +812,29 @@ public void testToObservable() { ts.assertCompleted(); } + @Test + public void toCompletableSuccess() { + Completable completable = Single.just("value").toCompletable(); + TestSubscriber testSubscriber = new TestSubscriber(); + completable.subscribe(testSubscriber); + + testSubscriber.assertCompleted(); + testSubscriber.assertNoValues(); + testSubscriber.assertNoErrors(); + } + + @Test + public void toCompletableError() { + TestException exception = new TestException(); + Completable completable = Single.error(exception).toCompletable(); + TestSubscriber testSubscriber = new TestSubscriber(); + completable.subscribe(testSubscriber); + + testSubscriber.assertError(exception); + testSubscriber.assertNoValues(); + testSubscriber.assertNotCompleted(); + } + @Test public void doOnErrorShouldNotCallActionIfNoErrorHasOccurred() { Action1 action = mock(Action1.class); From 8af2bc96808267b3e3d07021ab4e95a8467a05b9 Mon Sep 17 00:00:00 2001 From: Shixiong Zhu Date: Wed, 20 Apr 2016 10:03:04 -0700 Subject: [PATCH 027/294] Fix an unsubscribe race in EventLoopWorker (#3868) There is an unsubscribe race condition similar to #3842 in `CachedThreadScheduler.EventLoopWorker` and `EventLoopsScheduler.EventLoopWorker`. Image the following execution order: | Execution Order | thread 1 | thread 2 | | ------------- | ------------- | ------------- | | 1 | | submit task A | | 2 | | submit task B | | 3 | unsubscribe Worker | | | 4 | unsubscribe task A | | | 5 | | task A won't run as it's unsubscribed | | 6 | | run task B | | 7 | unsubscribe task B | | So task B will run but its previous task A will be skipped. This PR adds a check before running an action and moves `workerUnderConcurrentUnsubscribeShouldNotAllowLaterTasksToRunDueToUnsubscriptionRace` to `AbstractSchedulerConcurrencyTests` to test all concurrent schedulers. --- .../schedulers/CachedThreadScheduler.java | 12 +++++-- .../schedulers/EventLoopsScheduler.java | 27 +++++++++++++--- .../AbstractSchedulerConcurrencyTests.java | 31 +++++++++++++++++++ .../rx/schedulers/ExecutorSchedulerTest.java | 30 ------------------ 4 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/main/java/rx/internal/schedulers/CachedThreadScheduler.java b/src/main/java/rx/internal/schedulers/CachedThreadScheduler.java index 8e131e58e5..472ade9109 100644 --- a/src/main/java/rx/internal/schedulers/CachedThreadScheduler.java +++ b/src/main/java/rx/internal/schedulers/CachedThreadScheduler.java @@ -204,13 +204,21 @@ public Subscription schedule(Action0 action) { } @Override - public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { + public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit) { if (innerSubscription.isUnsubscribed()) { // don't schedule, we are unsubscribed return Subscriptions.unsubscribed(); } - ScheduledAction s = threadWorker.scheduleActual(action, delayTime, unit); + ScheduledAction s = threadWorker.scheduleActual(new Action0() { + @Override + public void call() { + if (isUnsubscribed()) { + return; + } + action.call(); + } + }, delayTime, unit); innerSubscription.add(s); s.addParent(innerSubscription); return s; diff --git a/src/main/java/rx/internal/schedulers/EventLoopsScheduler.java b/src/main/java/rx/internal/schedulers/EventLoopsScheduler.java index afcf7464ed..1eef164937 100644 --- a/src/main/java/rx/internal/schedulers/EventLoopsScheduler.java +++ b/src/main/java/rx/internal/schedulers/EventLoopsScheduler.java @@ -156,20 +156,37 @@ public boolean isUnsubscribed() { } @Override - public Subscription schedule(Action0 action) { + public Subscription schedule(final Action0 action) { if (isUnsubscribed()) { return Subscriptions.unsubscribed(); } - return poolWorker.scheduleActual(action, 0, null, serial); + return poolWorker.scheduleActual(new Action0() { + @Override + public void call() { + if (isUnsubscribed()) { + return; + } + action.call(); + } + }, 0, null, serial); } + @Override - public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { + public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit) { if (isUnsubscribed()) { return Subscriptions.unsubscribed(); } - return poolWorker.scheduleActual(action, delayTime, unit, timed); + return poolWorker.scheduleActual(new Action0() { + @Override + public void call() { + if (isUnsubscribed()) { + return; + } + action.call(); + } + }, delayTime, unit, timed); } } @@ -178,4 +195,4 @@ static final class PoolWorker extends NewThreadWorker { super(threadFactory); } } -} \ No newline at end of file +} diff --git a/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java b/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java index 2eab100310..9e3fdf94d3 100644 --- a/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java +++ b/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java @@ -20,6 +20,8 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -423,4 +425,33 @@ public void call(Integer t) { assertEquals(5, count.get()); } + @Test + public void workerUnderConcurrentUnsubscribeShouldNotAllowLaterTasksToRunDueToUnsubscriptionRace() { + Scheduler scheduler = getScheduler(); + for (int i = 0; i < 1000; i++) { + Worker worker = scheduler.createWorker(); + final Queue q = new ConcurrentLinkedQueue(); + Action0 action1 = new Action0() { + + @Override + public void call() { + q.add(1); + } + }; + Action0 action2 = new Action0() { + + @Override + public void call() { + q.add(2); + } + }; + worker.schedule(action1); + worker.schedule(action2); + worker.unsubscribe(); + if (q.size() == 1 && q.poll() == 2) { + //expect a queue of 1,2 or 1. If queue is just 2 then we have a problem! + fail("wrong order on loop " + i); + } + } + } } diff --git a/src/test/java/rx/schedulers/ExecutorSchedulerTest.java b/src/test/java/rx/schedulers/ExecutorSchedulerTest.java index 0777208cab..ed4e03213d 100644 --- a/src/test/java/rx/schedulers/ExecutorSchedulerTest.java +++ b/src/test/java/rx/schedulers/ExecutorSchedulerTest.java @@ -18,11 +18,9 @@ import static org.junit.Assert.*; import java.lang.management.*; -import java.util.Queue; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Assert; import org.junit.Test; import rx.*; @@ -277,32 +275,4 @@ public void call() { assertFalse(w.tasks.hasSubscriptions()); } - - @Test - public void workerUnderConcurrentUnsubscribeShouldNotAllowLaterTasksToRunDueToUnsubscriptionRace() { - Scheduler scheduler = Schedulers.from(Executors.newFixedThreadPool(1)); - for (int i = 0; i< 1000; i++) { - Worker worker = scheduler.createWorker(); - final Queue q = new ConcurrentLinkedQueue(); - Action0 action1 = new Action0() { - - @Override - public void call() { - q.add(1); - }}; - Action0 action2 = new Action0() { - - @Override - public void call() { - q.add(2); - }}; - worker.schedule(action1); - worker.schedule(action2); - worker.unsubscribe(); - if (q.size()==1 && q.poll() == 2) { - //expect a queue of 1,2 or 1. If queue is just 2 then we have a problem! - Assert.fail("wrong order on loop " + i); - } - } - } } From 3439dd8f73affa9b80277e72777ec3f15baafcfc Mon Sep 17 00:00:00 2001 From: Dave Moten Date: Thu, 21 Apr 2016 03:15:37 +1000 Subject: [PATCH 028/294] ensure waiting tasks are cancelled on worker unsubscription (#3867) --- src/main/java/rx/schedulers/ExecutorScheduler.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/rx/schedulers/ExecutorScheduler.java b/src/main/java/rx/schedulers/ExecutorScheduler.java index 70f217e49b..11d10214f9 100644 --- a/src/main/java/rx/schedulers/ExecutorScheduler.java +++ b/src/main/java/rx/schedulers/ExecutorScheduler.java @@ -100,14 +100,17 @@ public void run() { queue.clear(); return; } - ScheduledAction sa = queue.poll(); if (sa == null) { return; } - if (!sa.isUnsubscribed()) { - sa.run(); + if (!tasks.isUnsubscribed()) { + sa.run(); + } else { + queue.clear(); + return; + } } } while (wip.decrementAndGet() != 0); } From 95389c260fe93972421bfe8092bceb283d0db346 Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Thu, 21 Apr 2016 03:06:35 -0400 Subject: [PATCH 029/294] Deprecate remaining public scheduler types. (#3871) --- .../internal/operators/OperatorObserveOn.java | 2 +- .../schedulers/ExecutorScheduler.java | 5 +- .../GenericScheduledExecutorService.java | 1 - .../schedulers/ImmediateScheduler.java | 73 ++++++++++ .../schedulers/SleepingAction.java | 2 +- .../schedulers/TrampolineScheduler.java | 131 ++++++++++++++++++ .../rx/schedulers/ImmediateScheduler.java | 55 +------- .../rx/schedulers/NewThreadScheduler.java | 1 + src/main/java/rx/schedulers/Schedulers.java | 14 +- .../rx/schedulers/TrampolineScheduler.java | 113 +-------------- .../schedulers/ExecutorSchedulerTest.java | 81 +---------- .../schedulers/ComputationSchedulerTests.java | 4 +- ...chedulerTest.java => IoSchedulerTest.java} | 8 +- .../java/rx/schedulers/SchedulerTests.java | 78 ++++++++++- 14 files changed, 316 insertions(+), 252 deletions(-) rename src/main/java/rx/{ => internal}/schedulers/ExecutorScheduler.java (98%) create mode 100644 src/main/java/rx/internal/schedulers/ImmediateScheduler.java rename src/main/java/rx/{ => internal}/schedulers/SleepingAction.java (98%) create mode 100644 src/main/java/rx/internal/schedulers/TrampolineScheduler.java rename src/test/java/rx/{ => internal}/schedulers/ExecutorSchedulerTest.java (70%) rename src/test/java/rx/schedulers/{CachedThreadSchedulerTest.java => IoSchedulerTest.java} (91%) diff --git a/src/main/java/rx/internal/operators/OperatorObserveOn.java b/src/main/java/rx/internal/operators/OperatorObserveOn.java index 1720e1dfe2..f09a424020 100644 --- a/src/main/java/rx/internal/operators/OperatorObserveOn.java +++ b/src/main/java/rx/internal/operators/OperatorObserveOn.java @@ -22,11 +22,11 @@ import rx.Observable.Operator; import rx.exceptions.MissingBackpressureException; import rx.functions.Action0; +import rx.internal.schedulers.*; import rx.internal.util.*; import rx.internal.util.atomic.SpscAtomicArrayQueue; import rx.internal.util.unsafe.*; import rx.plugins.RxJavaPlugins; -import rx.schedulers.*; /** * Delivers events on the specified {@code Scheduler} asynchronously via an unbounded buffer. diff --git a/src/main/java/rx/schedulers/ExecutorScheduler.java b/src/main/java/rx/internal/schedulers/ExecutorScheduler.java similarity index 98% rename from src/main/java/rx/schedulers/ExecutorScheduler.java rename to src/main/java/rx/internal/schedulers/ExecutorScheduler.java index 11d10214f9..b40c0b4900 100644 --- a/src/main/java/rx/schedulers/ExecutorScheduler.java +++ b/src/main/java/rx/internal/schedulers/ExecutorScheduler.java @@ -13,14 +13,13 @@ * License for the specific language governing permissions and limitations under * the License. */ -package rx.schedulers; +package rx.internal.schedulers; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import rx.*; import rx.functions.Action0; -import rx.internal.schedulers.*; import rx.plugins.RxJavaPlugins; import rx.subscriptions.*; @@ -30,7 +29,7 @@ * Note that thread-hopping is unavoidable with this kind of Scheduler as we don't know about the underlying * threading behavior of the executor. */ -/* public */final class ExecutorScheduler extends Scheduler { +public final class ExecutorScheduler extends Scheduler { final Executor executor; public ExecutorScheduler(Executor executor) { this.executor = executor; diff --git a/src/main/java/rx/internal/schedulers/GenericScheduledExecutorService.java b/src/main/java/rx/internal/schedulers/GenericScheduledExecutorService.java index e322945068..3bc60f076b 100644 --- a/src/main/java/rx/internal/schedulers/GenericScheduledExecutorService.java +++ b/src/main/java/rx/internal/schedulers/GenericScheduledExecutorService.java @@ -20,7 +20,6 @@ import rx.Scheduler; import rx.internal.util.RxThreadFactory; -import rx.schedulers.*; /** * A default {@link ScheduledExecutorService} that can be used for scheduling actions when a {@link Scheduler} implementation doesn't have that ability. diff --git a/src/main/java/rx/internal/schedulers/ImmediateScheduler.java b/src/main/java/rx/internal/schedulers/ImmediateScheduler.java new file mode 100644 index 0000000000..c3dd7a6f85 --- /dev/null +++ b/src/main/java/rx/internal/schedulers/ImmediateScheduler.java @@ -0,0 +1,73 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.internal.schedulers; + +import java.util.concurrent.TimeUnit; + +import rx.Scheduler; +import rx.Subscription; +import rx.functions.Action0; +import rx.subscriptions.BooleanSubscription; +import rx.subscriptions.Subscriptions; + +/** + * Executes work immediately on the current thread. + */ +public final class ImmediateScheduler extends Scheduler { + public static final ImmediateScheduler INSTANCE = new ImmediateScheduler(); + + private ImmediateScheduler() { + } + + @Override + public Worker createWorker() { + return new InnerImmediateScheduler(); + } + + private class InnerImmediateScheduler extends Scheduler.Worker implements Subscription { + + final BooleanSubscription innerSubscription = new BooleanSubscription(); + + InnerImmediateScheduler() { + } + + @Override + public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { + // since we are executing immediately on this thread we must cause this thread to sleep + long execTime = ImmediateScheduler.this.now() + unit.toMillis(delayTime); + + return schedule(new SleepingAction(action, this, execTime)); + } + + @Override + public Subscription schedule(Action0 action) { + action.call(); + return Subscriptions.unsubscribed(); + } + + @Override + public void unsubscribe() { + innerSubscription.unsubscribe(); + } + + @Override + public boolean isUnsubscribed() { + return innerSubscription.isUnsubscribed(); + } + + } + +} diff --git a/src/main/java/rx/schedulers/SleepingAction.java b/src/main/java/rx/internal/schedulers/SleepingAction.java similarity index 98% rename from src/main/java/rx/schedulers/SleepingAction.java rename to src/main/java/rx/internal/schedulers/SleepingAction.java index bb13734475..c41c01caf0 100644 --- a/src/main/java/rx/schedulers/SleepingAction.java +++ b/src/main/java/rx/internal/schedulers/SleepingAction.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package rx.schedulers; +package rx.internal.schedulers; import rx.Scheduler; import rx.functions.Action0; diff --git a/src/main/java/rx/internal/schedulers/TrampolineScheduler.java b/src/main/java/rx/internal/schedulers/TrampolineScheduler.java new file mode 100644 index 0000000000..94fea1964a --- /dev/null +++ b/src/main/java/rx/internal/schedulers/TrampolineScheduler.java @@ -0,0 +1,131 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.internal.schedulers; + +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import rx.Scheduler; +import rx.Subscription; +import rx.functions.Action0; +import rx.subscriptions.BooleanSubscription; +import rx.subscriptions.Subscriptions; + +/** + * Schedules work on the current thread but does not execute immediately. Work is put in a queue and executed + * after the current unit of work is completed. + */ +public final class TrampolineScheduler extends Scheduler { + public static final TrampolineScheduler INSTANCE = new TrampolineScheduler(); + + @Override + public Worker createWorker() { + return new InnerCurrentThreadScheduler(); + } + + private TrampolineScheduler() { + } + + private static class InnerCurrentThreadScheduler extends Scheduler.Worker implements Subscription { + + final AtomicInteger counter = new AtomicInteger(); + final PriorityBlockingQueue queue = new PriorityBlockingQueue(); + private final BooleanSubscription innerSubscription = new BooleanSubscription(); + private final AtomicInteger wip = new AtomicInteger(); + + InnerCurrentThreadScheduler() { + } + + @Override + public Subscription schedule(Action0 action) { + return enqueue(action, now()); + } + + @Override + public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { + long execTime = now() + unit.toMillis(delayTime); + + return enqueue(new SleepingAction(action, this, execTime), execTime); + } + + private Subscription enqueue(Action0 action, long execTime) { + if (innerSubscription.isUnsubscribed()) { + return Subscriptions.unsubscribed(); + } + final TimedAction timedAction = new TimedAction(action, execTime, counter.incrementAndGet()); + queue.add(timedAction); + + if (wip.getAndIncrement() == 0) { + do { + final TimedAction polled = queue.poll(); + if (polled != null) { + polled.action.call(); + } + } while (wip.decrementAndGet() > 0); + return Subscriptions.unsubscribed(); + } else { + // queue wasn't empty, a parent is already processing so we just add to the end of the queue + return Subscriptions.create(new Action0() { + + @Override + public void call() { + queue.remove(timedAction); + } + + }); + } + } + + @Override + public void unsubscribe() { + innerSubscription.unsubscribe(); + } + + @Override + public boolean isUnsubscribed() { + return innerSubscription.isUnsubscribed(); + } + + } + + private static final class TimedAction implements Comparable { + final Action0 action; + final Long execTime; + final int count; // In case if time between enqueueing took less than 1ms + + TimedAction(Action0 action, Long execTime, int count) { + this.action = action; + this.execTime = execTime; + this.count = count; + } + + @Override + public int compareTo(TimedAction that) { + int result = execTime.compareTo(that.execTime); + if (result == 0) { + return compare(count, that.count); + } + return result; + } + } + + // because I can't use Integer.compare from Java 7 + static int compare(int x, int y) { + return (x < y) ? -1 : ((x == y) ? 0 : 1); + } + +} diff --git a/src/main/java/rx/schedulers/ImmediateScheduler.java b/src/main/java/rx/schedulers/ImmediateScheduler.java index e480754a58..3be8edda4a 100644 --- a/src/main/java/rx/schedulers/ImmediateScheduler.java +++ b/src/main/java/rx/schedulers/ImmediateScheduler.java @@ -15,63 +15,20 @@ */ package rx.schedulers; -import java.util.concurrent.TimeUnit; - import rx.Scheduler; -import rx.Subscription; -import rx.functions.Action0; -import rx.subscriptions.BooleanSubscription; -import rx.subscriptions.Subscriptions; /** - * Executes work immediately on the current thread. + * @deprecated This type was never publicly instantiable. Use {@link Schedulers#immediate()}. */ +@Deprecated +@SuppressWarnings("unused") // Class was part of public API. public final class ImmediateScheduler extends Scheduler { - private static final ImmediateScheduler INSTANCE = new ImmediateScheduler(); - - /* package */static ImmediateScheduler instance() { - return INSTANCE; - } - - /* package accessible for unit tests */ImmediateScheduler() { + private ImmediateScheduler() { + throw new AssertionError(); } @Override public Worker createWorker() { - return new InnerImmediateScheduler(); + return null; } - - private class InnerImmediateScheduler extends Scheduler.Worker implements Subscription { - - final BooleanSubscription innerSubscription = new BooleanSubscription(); - - InnerImmediateScheduler() { - } - - @Override - public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { - // since we are executing immediately on this thread we must cause this thread to sleep - long execTime = ImmediateScheduler.this.now() + unit.toMillis(delayTime); - - return schedule(new SleepingAction(action, this, execTime)); - } - - @Override - public Subscription schedule(Action0 action) { - action.call(); - return Subscriptions.unsubscribed(); - } - - @Override - public void unsubscribe() { - innerSubscription.unsubscribe(); - } - - @Override - public boolean isUnsubscribed() { - return innerSubscription.isUnsubscribed(); - } - - } - } diff --git a/src/main/java/rx/schedulers/NewThreadScheduler.java b/src/main/java/rx/schedulers/NewThreadScheduler.java index 5dc6046268..192f8b29a8 100644 --- a/src/main/java/rx/schedulers/NewThreadScheduler.java +++ b/src/main/java/rx/schedulers/NewThreadScheduler.java @@ -21,6 +21,7 @@ * @deprecated This type was never publicly instantiable. Use {@link Schedulers#newThread()}. */ @Deprecated +@SuppressWarnings("unused") // Class was part of public API. public final class NewThreadScheduler extends Scheduler { private NewThreadScheduler() { throw new AssertionError(); diff --git a/src/main/java/rx/schedulers/Schedulers.java b/src/main/java/rx/schedulers/Schedulers.java index f81235347c..eae594ef08 100644 --- a/src/main/java/rx/schedulers/Schedulers.java +++ b/src/main/java/rx/schedulers/Schedulers.java @@ -61,21 +61,21 @@ private Schedulers() { /** * Creates and returns a {@link Scheduler} that executes work immediately on the current thread. - * - * @return an {@link ImmediateScheduler} instance + * + * @return a {@link Scheduler} that executes work immediately */ public static Scheduler immediate() { - return ImmediateScheduler.instance(); + return rx.internal.schedulers.ImmediateScheduler.INSTANCE; } /** * Creates and returns a {@link Scheduler} that queues work on the current thread to be executed after the * current work completes. - * - * @return a {@link TrampolineScheduler} instance + * + * @return a {@link Scheduler} that queues work on the current thread */ public static Scheduler trampoline() { - return TrampolineScheduler.instance(); + return rx.internal.schedulers.TrampolineScheduler.INSTANCE; } /** @@ -83,7 +83,7 @@ public static Scheduler trampoline() { *

* Unhandled errors will be delivered to the scheduler Thread's {@link java.lang.Thread.UncaughtExceptionHandler}. * - * @return a {@link NewThreadScheduler} instance + * @return a {@link Scheduler} that creates new threads */ public static Scheduler newThread() { return INSTANCE.newThreadScheduler; diff --git a/src/main/java/rx/schedulers/TrampolineScheduler.java b/src/main/java/rx/schedulers/TrampolineScheduler.java index 45bb18546c..5f708cdc23 100644 --- a/src/main/java/rx/schedulers/TrampolineScheduler.java +++ b/src/main/java/rx/schedulers/TrampolineScheduler.java @@ -15,121 +15,20 @@ */ package rx.schedulers; -import java.util.concurrent.PriorityBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - import rx.Scheduler; -import rx.Subscription; -import rx.functions.Action0; -import rx.subscriptions.BooleanSubscription; -import rx.subscriptions.Subscriptions; /** - * Schedules work on the current thread but does not execute immediately. Work is put in a queue and executed - * after the current unit of work is completed. + * @deprecated This type was never publicly instantiable. Use {@link Schedulers#trampoline()}. */ +@Deprecated +@SuppressWarnings("unused") // Class was part of public API. public final class TrampolineScheduler extends Scheduler { - private static final TrampolineScheduler INSTANCE = new TrampolineScheduler(); - - /* package */static TrampolineScheduler instance() { - return INSTANCE; + private TrampolineScheduler() { + throw new AssertionError(); } @Override public Worker createWorker() { - return new InnerCurrentThreadScheduler(); - } - - /* package accessible for unit tests */TrampolineScheduler() { + return null; } - - private static class InnerCurrentThreadScheduler extends Scheduler.Worker implements Subscription { - - final AtomicInteger counter = new AtomicInteger(); - final PriorityBlockingQueue queue = new PriorityBlockingQueue(); - private final BooleanSubscription innerSubscription = new BooleanSubscription(); - private final AtomicInteger wip = new AtomicInteger(); - - InnerCurrentThreadScheduler() { - } - - @Override - public Subscription schedule(Action0 action) { - return enqueue(action, now()); - } - - @Override - public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { - long execTime = now() + unit.toMillis(delayTime); - - return enqueue(new SleepingAction(action, this, execTime), execTime); - } - - private Subscription enqueue(Action0 action, long execTime) { - if (innerSubscription.isUnsubscribed()) { - return Subscriptions.unsubscribed(); - } - final TimedAction timedAction = new TimedAction(action, execTime, counter.incrementAndGet()); - queue.add(timedAction); - - if (wip.getAndIncrement() == 0) { - do { - final TimedAction polled = queue.poll(); - if (polled != null) { - polled.action.call(); - } - } while (wip.decrementAndGet() > 0); - return Subscriptions.unsubscribed(); - } else { - // queue wasn't empty, a parent is already processing so we just add to the end of the queue - return Subscriptions.create(new Action0() { - - @Override - public void call() { - queue.remove(timedAction); - } - - }); - } - } - - @Override - public void unsubscribe() { - innerSubscription.unsubscribe(); - } - - @Override - public boolean isUnsubscribed() { - return innerSubscription.isUnsubscribed(); - } - - } - - private static final class TimedAction implements Comparable { - final Action0 action; - final Long execTime; - final int count; // In case if time between enqueueing took less than 1ms - - TimedAction(Action0 action, Long execTime, int count) { - this.action = action; - this.execTime = execTime; - this.count = count; - } - - @Override - public int compareTo(TimedAction that) { - int result = execTime.compareTo(that.execTime); - if (result == 0) { - return compare(count, that.count); - } - return result; - } - } - - // because I can't use Integer.compare from Java 7 - static int compare(int x, int y) { - return (x < y) ? -1 : ((x == y) ? 0 : 1); - } - } diff --git a/src/test/java/rx/schedulers/ExecutorSchedulerTest.java b/src/test/java/rx/internal/schedulers/ExecutorSchedulerTest.java similarity index 70% rename from src/test/java/rx/schedulers/ExecutorSchedulerTest.java rename to src/test/java/rx/internal/schedulers/ExecutorSchedulerTest.java index ed4e03213d..fad2435ce1 100644 --- a/src/test/java/rx/schedulers/ExecutorSchedulerTest.java +++ b/src/test/java/rx/internal/schedulers/ExecutorSchedulerTest.java @@ -13,22 +13,21 @@ * License for the specific language governing permissions and limitations under * the License. */ -package rx.schedulers; +package rx.internal.schedulers; import static org.junit.Assert.*; -import java.lang.management.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; - import org.junit.Test; - import rx.*; import rx.Scheduler.Worker; import rx.functions.*; -import rx.internal.schedulers.NewThreadWorker; +import rx.internal.schedulers.ExecutorScheduler.ExecutorSchedulerWorker; import rx.internal.util.RxThreadFactory; -import rx.schedulers.ExecutorScheduler.ExecutorSchedulerWorker; +import rx.schedulers.AbstractSchedulerConcurrencyTests; +import rx.schedulers.SchedulerTests; +import rx.schedulers.Schedulers; public class ExecutorSchedulerTest extends AbstractSchedulerConcurrencyTests { @@ -49,72 +48,6 @@ public final void testHandledErrorIsNotDeliveredToThreadHandler() throws Interru SchedulerTests.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 = 500 * 1000; - if (periodic) { - final CountDownLatch cdl = new CountDownLatch(n); - final Action0 action = new Action0() { - @Override - public void call() { - 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(Actions.empty(), 1, TimeUnit.DAYS); - } - } - - memHeap = memoryMXBean.getHeapMemoryUsage(); - long after = memHeap.getUsed(); - System.out.printf("Peak: %.3f MB%n", after / 1024.0 / 1024.0); - - w.unsubscribe(); - - System.out.println("Wait before second GC"); - Thread.sleep(NewThreadWorker.PURGE_FREQUENCY + 2000); - - 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 = 30000) public void testCancelledTaskRetention() throws InterruptedException { ExecutorService exec = Executors.newSingleThreadExecutor(); @@ -122,14 +55,14 @@ public void testCancelledTaskRetention() throws InterruptedException { try { Scheduler.Worker w = s.createWorker(); try { - testCancelledRetention(w, false); + SchedulerTests.testCancelledRetention(w, false); } finally { w.unsubscribe(); } w = s.createWorker(); try { - testCancelledRetention(w, true); + SchedulerTests.testCancelledRetention(w, true); } finally { w.unsubscribe(); } diff --git a/src/test/java/rx/schedulers/ComputationSchedulerTests.java b/src/test/java/rx/schedulers/ComputationSchedulerTests.java index 7191f60015..7e9163e7c0 100644 --- a/src/test/java/rx/schedulers/ComputationSchedulerTests.java +++ b/src/test/java/rx/schedulers/ComputationSchedulerTests.java @@ -157,13 +157,13 @@ public final void testHandledErrorIsNotDeliveredToThreadHandler() throws Interru public void testCancelledTaskRetention() throws InterruptedException { Worker w = Schedulers.computation().createWorker(); try { - ExecutorSchedulerTest.testCancelledRetention(w, false); + SchedulerTests.testCancelledRetention(w, false); } finally { w.unsubscribe(); } w = Schedulers.computation().createWorker(); try { - ExecutorSchedulerTest.testCancelledRetention(w, true); + SchedulerTests.testCancelledRetention(w, true); } finally { w.unsubscribe(); } diff --git a/src/test/java/rx/schedulers/CachedThreadSchedulerTest.java b/src/test/java/rx/schedulers/IoSchedulerTest.java similarity index 91% rename from src/test/java/rx/schedulers/CachedThreadSchedulerTest.java rename to src/test/java/rx/schedulers/IoSchedulerTest.java index 9abb52b7ec..a16a19a61c 100644 --- a/src/test/java/rx/schedulers/CachedThreadSchedulerTest.java +++ b/src/test/java/rx/schedulers/IoSchedulerTest.java @@ -24,7 +24,7 @@ import rx.Scheduler.Worker; import rx.functions.*; -public class CachedThreadSchedulerTest extends AbstractSchedulerConcurrencyTests { +public class IoSchedulerTest extends AbstractSchedulerConcurrencyTests { @Override protected Scheduler getScheduler() { @@ -71,16 +71,16 @@ public final void testHandledErrorIsNotDeliveredToThreadHandler() throws Interru public void testCancelledTaskRetention() throws InterruptedException { Worker w = Schedulers.io().createWorker(); try { - ExecutorSchedulerTest.testCancelledRetention(w, false); + SchedulerTests.testCancelledRetention(w, false); } finally { w.unsubscribe(); } w = Schedulers.io().createWorker(); try { - ExecutorSchedulerTest.testCancelledRetention(w, true); + SchedulerTests.testCancelledRetention(w, true); } finally { w.unsubscribe(); } } -} \ No newline at end of file +} diff --git a/src/test/java/rx/schedulers/SchedulerTests.java b/src/test/java/rx/schedulers/SchedulerTests.java index a9146fafde..cd1d0a1912 100644 --- a/src/test/java/rx/schedulers/SchedulerTests.java +++ b/src/test/java/rx/schedulers/SchedulerTests.java @@ -4,14 +4,20 @@ import rx.Observable; import rx.Observer; import rx.Scheduler; +import rx.functions.Action0; +import rx.functions.Actions; +import rx.internal.schedulers.NewThreadWorker; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -final class SchedulerTests { +public final class SchedulerTests { private SchedulerTests() { // No instances. } @@ -23,7 +29,7 @@ private SchedulerTests() { * Schedulers which execute on a separate thread from their calling thread should exhibit this behavior. Schedulers * which execute on their calling thread may not. */ - static void testUnhandledErrorIsDeliveredToThreadHandler(Scheduler scheduler) throws InterruptedException { + public static void testUnhandledErrorIsDeliveredToThreadHandler(Scheduler scheduler) throws InterruptedException { Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler(); try { CapturingUncaughtExceptionHandler handler = new CapturingUncaughtExceptionHandler(); @@ -57,7 +63,7 @@ static void testUnhandledErrorIsDeliveredToThreadHandler(Scheduler scheduler) th * This is a companion test to {@link #testUnhandledErrorIsDeliveredToThreadHandler}, and is needed only for the * same Schedulers. */ - static void testHandledErrorIsNotDeliveredToThreadHandler(Scheduler scheduler) throws InterruptedException { + public static void testHandledErrorIsNotDeliveredToThreadHandler(Scheduler scheduler) throws InterruptedException { Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler(); try { CapturingUncaughtExceptionHandler handler = new CapturingUncaughtExceptionHandler(); @@ -88,6 +94,72 @@ static void testHandledErrorIsNotDeliveredToThreadHandler(Scheduler scheduler) t } } + 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 = 500 * 1000; + if (periodic) { + final CountDownLatch cdl = new CountDownLatch(n); + final Action0 action = new Action0() { + @Override + public void call() { + 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(Actions.empty(), 1, TimeUnit.DAYS); + } + } + + memHeap = memoryMXBean.getHeapMemoryUsage(); + long after = memHeap.getUsed(); + System.out.printf("Peak: %.3f MB%n", after / 1024.0 / 1024.0); + + w.unsubscribe(); + + System.out.println("Wait before second GC"); + Thread.sleep(NewThreadWorker.PURGE_FREQUENCY + 2000); + + 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)); + } + } + private static final class CapturingObserver implements Observer { CountDownLatch completed = new CountDownLatch(1); int errorCount = 0; From 3f6c4fd164801fd994034f99e663dc17163617c9 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Thu, 21 Apr 2016 21:58:08 +0200 Subject: [PATCH 030/294] 1.x: fix from(Iterable) error handling of Iterable/Iterator (#3862) * 1.x: fix from(Iterable) error handling of Iterable/Iterator * Check dead-on-arrival Subscribers * Use n again to avoid a potential cache-miss with get() --- .../operators/OnSubscribeFromIterable.java | 130 +++++++--- .../OnSubscribeFromIterableTest.java | 244 +++++++++++++++++- 2 files changed, 325 insertions(+), 49 deletions(-) diff --git a/src/main/java/rx/internal/operators/OnSubscribeFromIterable.java b/src/main/java/rx/internal/operators/OnSubscribeFromIterable.java index b94e35c35c..9389c4480c 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeFromIterable.java +++ b/src/main/java/rx/internal/operators/OnSubscribeFromIterable.java @@ -20,6 +20,7 @@ import rx.*; import rx.Observable.OnSubscribe; +import rx.exceptions.Exceptions; /** * Converts an {@code Iterable} sequence into an {@code Observable}. @@ -42,11 +43,25 @@ public OnSubscribeFromIterable(Iterable iterable) { @Override public void call(final Subscriber o) { - final Iterator it = is.iterator(); - if (!it.hasNext() && !o.isUnsubscribed()) - o.onCompleted(); - else - o.setProducer(new IterableProducer(o, it)); + final Iterator it; + boolean b; + + try { + it = is.iterator(); + + b = it.hasNext(); + } catch (Throwable ex) { + Exceptions.throwOrReport(ex, o); + return; + } + + if (!o.isUnsubscribed()) { + if (!b) { + o.onCompleted(); + } else { + o.setProducer(new IterableProducer(o, it)); + } + } } private static final class IterableProducer extends AtomicLong implements Producer { @@ -81,38 +96,58 @@ void slowpath(long n) { final Iterator it = this.it; long r = n; - while (true) { - /* - * This complicated logic is done to avoid touching the - * volatile `requested` value during the loop itself. If - * it is touched during the loop the performance is - * impacted significantly. - */ - long numToEmit = r; - while (true) { + long e = 0; + + for (;;) { + while (e != r) { if (o.isUnsubscribed()) { return; - } else if (it.hasNext()) { - if (--numToEmit >= 0) { - o.onNext(it.next()); - } else - break; - } else if (!o.isUnsubscribed()) { - o.onCompleted(); + } + + T value; + + try { + value = it.next(); + } catch (Throwable ex) { + Exceptions.throwOrReport(ex, o); return; - } else { - // is unsubscribed + } + + o.onNext(value); + + if (o.isUnsubscribed()) { return; } + + boolean b; + + try { + b = it.hasNext(); + } catch (Throwable ex) { + Exceptions.throwOrReport(ex, o); + return; + } + + if (!b) { + if (!o.isUnsubscribed()) { + o.onCompleted(); + } + return; + } + + e++; } - r = addAndGet(-r); - if (r == 0L) { - // we're done emitting the number requested so - // return - return; + + r = get(); + if (e == r) { + r = BackpressureUtils.produced(this, e); + if (r == 0L) { + break; + } + e = 0L; } - } + } void fastpath() { @@ -120,16 +155,39 @@ void fastpath() { final Subscriber o = this.o; final Iterator it = this.it; - while (true) { + for (;;) { if (o.isUnsubscribed()) { return; - } else if (it.hasNext()) { - o.onNext(it.next()); - } else if (!o.isUnsubscribed()) { - o.onCompleted(); + } + + T value; + + try { + value = it.next(); + } catch (Throwable ex) { + Exceptions.throwOrReport(ex, o); + return; + } + + o.onNext(value); + + if (o.isUnsubscribed()) { return; - } else { - // is unsubscribed + } + + boolean b; + + try { + b = it.hasNext(); + } catch (Throwable ex) { + Exceptions.throwOrReport(ex, o); + return; + } + + if (!b) { + if (!o.isUnsubscribed()) { + o.onCompleted(); + } return; } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeFromIterableTest.java b/src/test/java/rx/internal/operators/OnSubscribeFromIterableTest.java index a75e733951..00956b9cae 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeFromIterableTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeFromIterableTest.java @@ -15,28 +15,21 @@ */ package rx.internal.operators; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import static org.mockito.Mockito.*; + +import java.util.*; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import rx.Observable; import rx.Observer; import rx.Subscriber; +import rx.exceptions.TestException; import rx.internal.util.RxRingBuffer; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; @@ -313,5 +306,230 @@ public void onNext(Integer t) { }); assertFalse(called.get()); } + + @Test + public void getIteratorThrows() { + Iterable it = new Iterable() { + @Override + public Iterator iterator() { + throw new TestException("Forced failure"); + } + }; + + TestSubscriber ts = new TestSubscriber(); + + Observable.from(it).unsafeSubscribe(ts); + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void hasNextThrowsImmediately() { + Iterable it = new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { + throw new TestException("Forced failure"); + } + + @Override + public Integer next() { + return null; + } + + @Override + public void remove() { + // ignored + } + }; + } + }; + + TestSubscriber ts = new TestSubscriber(); + + Observable.from(it).unsafeSubscribe(ts); + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void hasNextThrowsSecondTimeFastpath() { + Iterable it = new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + int count; + @Override + public boolean hasNext() { + if (++count >= 2) { + throw new TestException("Forced failure"); + } + return true; + } + + @Override + public Integer next() { + return 1; + } + + @Override + public void remove() { + // ignored + } + }; + } + }; + + TestSubscriber ts = new TestSubscriber(); + + Observable.from(it).unsafeSubscribe(ts); + + ts.assertValues(1); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void hasNextThrowsSecondTimeSlowpath() { + Iterable it = new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + int count; + @Override + public boolean hasNext() { + if (++count >= 2) { + throw new TestException("Forced failure"); + } + return true; + } + + @Override + public Integer next() { + return 1; + } + + @Override + public void remove() { + // ignored + } + }; + } + }; + + TestSubscriber ts = new TestSubscriber(5); + + Observable.from(it).unsafeSubscribe(ts); + + ts.assertValues(1); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + @Test + public void nextThrowsFastpath() { + Iterable it = new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { + return true; + } + + @Override + public Integer next() { + throw new TestException("Forced failure"); + } + + @Override + public void remove() { + // ignored + } + }; + } + }; + + TestSubscriber ts = new TestSubscriber(); + + Observable.from(it).unsafeSubscribe(ts); + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void nextThrowsSlowpath() { + Iterable it = new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { + return true; + } + + @Override + public Integer next() { + throw new TestException("Forced failure"); + } + + @Override + public void remove() { + // ignored + } + }; + } + }; + + TestSubscriber ts = new TestSubscriber(5); + + Observable.from(it).unsafeSubscribe(ts); + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void deadOnArrival() { + Iterable it = new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { + return false; + } + + @Override + public Integer next() { + throw new NoSuchElementException(); + } + + @Override + public void remove() { + // ignored + } + }; + } + }; + + TestSubscriber ts = new TestSubscriber(5); + ts.unsubscribe(); + + Observable.from(it).unsafeSubscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + + } } From 7b11b1c5e47e22c9f24fc0ee5c3c9e89bb091903 Mon Sep 17 00:00:00 2001 From: Dave Moten Date: Sat, 23 Apr 2016 08:45:04 +1000 Subject: [PATCH 031/294] remove unused field baseCapacity (#3874) --- .../rx/internal/operators/OperatorOnBackpressureBuffer.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/rx/internal/operators/OperatorOnBackpressureBuffer.java b/src/main/java/rx/internal/operators/OperatorOnBackpressureBuffer.java index 4f66bbb4d7..04e6e81be9 100644 --- a/src/main/java/rx/internal/operators/OperatorOnBackpressureBuffer.java +++ b/src/main/java/rx/internal/operators/OperatorOnBackpressureBuffer.java @@ -110,7 +110,6 @@ public Subscriber call(final Subscriber child) { private static final class BufferSubscriber extends Subscriber implements BackpressureDrainManager.BackpressureQueueCallback { // TODO get a different queue implementation private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); - private final Long baseCapacity; private final AtomicLong capacity; private final Subscriber child; private final AtomicBoolean saturated = new AtomicBoolean(false); @@ -122,7 +121,6 @@ private static final class BufferSubscriber extends Subscriber implements public BufferSubscriber(final Subscriber child, Long capacity, Action0 onOverflow, BackpressureOverflow.Strategy overflowStrategy) { this.child = child; - this.baseCapacity = capacity; this.capacity = capacity != null ? new AtomicLong(capacity) : null; this.onOverflow = onOverflow; this.manager = new BackpressureDrainManager(this); From 9b0887017a09e3c1ee7e9c2df066d8b2da30db48 Mon Sep 17 00:00:00 2001 From: Daniel Lew Date: Fri, 29 Apr 2016 00:56:57 -0500 Subject: [PATCH 032/294] throwIfFatal() now throws OnCompletedFailedException (#3886) Otherwise, if there's an error in onCompleted, the exception is swallowed and unreported. --- src/main/java/rx/exceptions/Exceptions.java | 3 +++ .../java/rx/exceptions/ExceptionsTest.java | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/main/java/rx/exceptions/Exceptions.java b/src/main/java/rx/exceptions/Exceptions.java index 6c37167c3e..f427018f53 100644 --- a/src/main/java/rx/exceptions/Exceptions.java +++ b/src/main/java/rx/exceptions/Exceptions.java @@ -61,6 +61,7 @@ public static RuntimeException propagate(Throwable t) { *