From e6824c1ab2b649654bebabc3cedb5a15f7605141 Mon Sep 17 00:00:00 2001 From: Maksym Ostroverkhov Date: Sat, 18 Mar 2017 18:47:14 +0200 Subject: [PATCH 01/37] 1.X: UnicastSubject fail-fast and delay-error behavior (#5195) * 1.X: unicastSubject does not replay onNext calls made prior to subscription if onError is also called prior to subscription. * cache delayError field in local variable --- src/main/java/rx/subjects/UnicastSubject.java | 62 +++++++++-- .../java/rx/subjects/UnicastSubjectTest.java | 101 ++++++++++++++++++ 2 files changed, 153 insertions(+), 10 deletions(-) create mode 100644 src/test/java/rx/subjects/UnicastSubjectTest.java diff --git a/src/main/java/rx/subjects/UnicastSubject.java b/src/main/java/rx/subjects/UnicastSubject.java index c1c69f9e36..74bdf3d539 100644 --- a/src/main/java/rx/subjects/UnicastSubject.java +++ b/src/main/java/rx/subjects/UnicastSubject.java @@ -49,6 +49,7 @@ public final class UnicastSubject extends Subject { public static UnicastSubject create() { return create(16); } + /** * Constructs an empty UnicastSubject instance with a capacity hint. *

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

The capacity hint determines the internal queue's island size: the larger + * it is the less frequent allocation will happen if there is no subscriber + * or the subscriber hasn't caught up. + * @param the input and output value type + * @param capacityHint the capacity hint for the internal queue + * @param onTerminated the optional callback to call when subject reaches its terminal state + * or the single Subscriber unsubscribes mid-sequence. It will be called + * at most once. + * @param delayError flag indicating whether to deliver pending next events before error. + * @return the created BufferUntilSubscriber instance + */ + public static UnicastSubject create(int capacityHint, + Action0 onTerminated, boolean delayError) { + State state = new State(capacityHint, delayError, onTerminated); return new UnicastSubject(state); } @@ -119,6 +152,8 @@ static final class State extends AtomicLong implements Producer, Observer, final AtomicReference> subscriber; /** The queue holding values until the subscriber arrives and catches up. */ final Queue queue; + /** Deliver pending next events before error. */ + final boolean delayError; /** Atomically set to true on terminal condition. */ final AtomicReference terminateOnce; /** In case the source emitted an error. */ @@ -137,10 +172,12 @@ static final class State extends AtomicLong implements Producer, Observer, * reduce allocation frequency * @param onTerminated the action to call when the subject reaches its terminal state or * the single subscriber unsubscribes. + * @param delayError deliver pending next events before error. */ - public State(int capacityHint, Action0 onTerminated) { + public State(int capacityHint, boolean delayError, Action0 onTerminated) { this.subscriber = new AtomicReference>(); this.terminateOnce = onTerminated != null ? new AtomicReference(onTerminated) : null; + this.delayError = delayError; Queue q; if (capacityHint > 1) { @@ -266,14 +303,14 @@ void replay() { emitting = true; } Queue q = queue; + boolean delayError = this.delayError; for (;;) { Subscriber s = subscriber.get(); boolean unlimited = false; if (s != null) { boolean d = done; boolean empty = q.isEmpty(); - - if (checkTerminated(d, empty, s)) { + if (checkTerminated(d, empty, delayError, s)) { return; } long r = get(); @@ -284,7 +321,7 @@ void replay() { d = done; Object v = q.poll(); empty = v == null; - if (checkTerminated(d, empty, s)) { + if (checkTerminated(d, empty, delayError, s)) { return; } if (empty) { @@ -348,23 +385,28 @@ public boolean isUnsubscribed() { * an error happened or the source terminated and the queue is empty * @param done indicates the source has called onCompleted * @param empty indicates if there are no more source values in the queue + * @param delayError indicates whether to deliver pending next events before error * @param s the target Subscriber to emit events to * @return true if this Subject reached a terminal state and the drain loop should quit */ - boolean checkTerminated(boolean done, boolean empty, Subscriber s) { + boolean checkTerminated(boolean done, boolean empty, boolean delayError, Subscriber s) { if (s.isUnsubscribed()) { queue.clear(); return true; } if (done) { Throwable e = error; - if (e != null) { + if (e != null && !delayError) { queue.clear(); s.onError(e); return true; - } else + } if (empty) { - s.onCompleted(); + if (e != null) { + s.onError(e); + } else { + s.onCompleted(); + } return true; } } diff --git a/src/test/java/rx/subjects/UnicastSubjectTest.java b/src/test/java/rx/subjects/UnicastSubjectTest.java new file mode 100644 index 0000000000..af9ef33468 --- /dev/null +++ b/src/test/java/rx/subjects/UnicastSubjectTest.java @@ -0,0 +1,101 @@ +package rx.subjects; + +import org.junit.Test; +import rx.functions.Action0; +import rx.observers.TestSubscriber; + +public class UnicastSubjectTest { + + @Test + public void testOneArgFactoryDelayError() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(true); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(2); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testOneArgFactoryNoDelayError() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(false); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testThreeArgsFactoryDelayError() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(16, new NoopAction0(), true); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(2); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testThreeArgsFactoryNoDelayError() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(16, new NoopAction0(), false); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testZeroArgsFactory() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testOneArgFactory() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(16); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testTwoArgsFactory() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(16, new NoopAction0()); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + + + private static final class NoopAction0 implements Action0 { + + @Override + public void call() { + } + } +} From d1f20a16c50600097130a60ee7e710a7e7453607 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Tue, 21 Mar 2017 23:14:01 +0100 Subject: [PATCH 02/37] 1.x: add marble diagram to UnicastSubject, fix javadoc (#5209) --- src/main/java/rx/subjects/UnicastSubject.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/rx/subjects/UnicastSubject.java b/src/main/java/rx/subjects/UnicastSubject.java index 74bdf3d539..1c51fe48bb 100644 --- a/src/main/java/rx/subjects/UnicastSubject.java +++ b/src/main/java/rx/subjects/UnicastSubject.java @@ -32,7 +32,8 @@ * amount. In this case, the buffered values are no longer retained. If the Subscriber * requests a limited amount, queueing is involved and only those values are retained which * weren't requested by the Subscriber at that time. - * + *

+ * * @param the input and output value type */ @Experimental @@ -68,6 +69,7 @@ public static UnicastSubject create(int capacityHint) { * Constructs an empty UnicastSubject instance with the default capacity hint of 16 elements. * * @param delayError deliver pending next events before error. + * @param the input and output value type * @return the created UnicastSubject instance */ public static UnicastSubject create(boolean delayError) { From 72a6dee2d2b15b05d8448f83dfab98ab3d6c900f Mon Sep 17 00:00:00 2001 From: David Karnok Date: Fri, 24 Mar 2017 09:31:33 +0100 Subject: [PATCH 03/37] Release 1.2.8 --- CHANGES.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f47d459578..9bb7651edb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,16 @@ # RxJava Releases # +### Version 1.2.8 - March 24, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.8%7C)) + +#### API enhancements +- [Pull 5146](https://github.com/ReactiveX/RxJava/pull/5146): Add `Single.unsubscribeOn`. +- [Pull 5195](https://github.com/ReactiveX/RxJava/pull/5195): Enhance `UnicastSubject` with optional delay error behavior. + +#### Bugfixes + +- [Pull 5141](https://github.com/ReactiveX/RxJava/pull/5141): Fix timed `replay()` not terminating when all items timeout. +- [Pull 5181](https://github.com/ReactiveX/RxJava/pull/5181): `replay().refCount()` avoid leaking items between connections. + ### Version 1.2.7 - February 24, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.7%7C)) #### Deprecation of `create(OnSubscribe)` From 1d7edee0a60cbf185157555483db430e1afd6702 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Fri, 24 Mar 2017 10:48:10 +0100 Subject: [PATCH 04/37] Update CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 9bb7651edb..c0380e97dd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # RxJava Releases # -### Version 1.2.8 - March 24, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.8%7C)) +### Version 1.2.9 - March 24, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.9%7C)) #### API enhancements - [Pull 5146](https://github.com/ReactiveX/RxJava/pull/5146): Add `Single.unsubscribeOn`. From e37d1a75771f5abf8ffe1b1c330df34ed3bbac82 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Fri, 24 Mar 2017 20:39:31 +0100 Subject: [PATCH 05/37] 1.x: fix Completable.onErrorResumeNext unsubscribe not propagated (#5225) --- src/main/java/rx/Completable.java | 1 + .../operators/CompletableOnErrorXTest.java | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/test/java/rx/internal/operators/CompletableOnErrorXTest.java diff --git a/src/main/java/rx/Completable.java b/src/main/java/rx/Completable.java index 536509b39a..1fb6ad1f49 100644 --- a/src/main/java/rx/Completable.java +++ b/src/main/java/rx/Completable.java @@ -1729,6 +1729,7 @@ public final Completable onErrorResumeNext(final Func1 ps = PublishSubject.create(); + + AssertableSubscriber as = ps.toCompletable() + .onErrorResumeNext(new Func1() { + @Override + public Completable call(Throwable e) { + return Completable.complete(); + } + }) + .test(); + + assertTrue(ps.hasObservers()); + + as.unsubscribe(); + + assertFalse("Still subscribed!", ps.hasObservers()); + } + + @Test + public void completeUnsubscribe() { + PublishSubject ps = PublishSubject.create(); + + AssertableSubscriber as = ps.toCompletable() + .onErrorComplete() + .test(); + + assertTrue(ps.hasObservers()); + + as.unsubscribe(); + + assertFalse("Still subscribed!", ps.hasObservers()); + } +} From bc40a84ffd66852ffcc320f2a2430ef52c903f61 Mon Sep 17 00:00:00 2001 From: Dan Hendry Date: Sun, 2 Apr 2017 04:48:06 -0400 Subject: [PATCH 06/37] Defer creation of the TimeoutException when using the Single.timeout() operator (#5250) * Defer creation of the TimeoutException when using the Single.timeout() operator * Fix formatting --- src/main/java/rx/Single.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/rx/Single.java b/src/main/java/rx/Single.java index 6edb80ab15..0b39b6f24f 100644 --- a/src/main/java/rx/Single.java +++ b/src/main/java/rx/Single.java @@ -2254,7 +2254,15 @@ public final Single timeout(long timeout, TimeUnit timeUnit, Single timeout(long timeout, TimeUnit timeUnit, Single other, Scheduler scheduler) { if (other == null) { - other = Single. error(new TimeoutException()); + // Use a defer instead of simply other = Single.error(new TimeoutException()) + // since instantiating an exception will cause the current stack trace to be inspected + // and we only want to incur that overhead when a timeout actually happens. + other = Single.defer(new Func0>() { + @Override + public Single call() { + return Single.error(new TimeoutException()); + } + }); } return create(new SingleTimeout(onSubscribe, timeout, timeUnit, scheduler, other.onSubscribe)); } From 2162d6d35a8e162f408e1bfd4083924c0987751b Mon Sep 17 00:00:00 2001 From: Marko Tiidla Date: Sun, 2 Apr 2017 12:42:10 +0200 Subject: [PATCH 07/37] 1.x Use IntelliJ IDE friendly assertion failure message (#5258) * Test Intellij formatting fix * Fix test subscriber test case --- src/main/java/rx/observers/TestSubscriber.java | 4 ++-- src/test/java/rx/observers/TestSubscriberTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/rx/observers/TestSubscriber.java b/src/main/java/rx/observers/TestSubscriber.java index cfa1f74ef9..bb76878ba6 100644 --- a/src/main/java/rx/observers/TestSubscriber.java +++ b/src/main/java/rx/observers/TestSubscriber.java @@ -337,11 +337,11 @@ private void assertItem(T expected, int i) { if (expected == null) { // check for null equality if (actual != null) { - assertionError("Value at index: " + i + " expected to be [null] but was: [" + actual + "]\n"); + assertionError("Value at index: " + i + " expected: [null] but was: [" + actual + "]\n"); } } else if (!expected.equals(actual)) { assertionError("Value at index: " + i - + " expected to be [" + expected + "] (" + expected.getClass().getSimpleName() + + " expected: [" + expected + "] (" + expected.getClass().getSimpleName() + ") but was: [" + actual + "] (" + (actual != null ? actual.getClass().getSimpleName() : "null") + ")\n"); } diff --git a/src/test/java/rx/observers/TestSubscriberTest.java b/src/test/java/rx/observers/TestSubscriberTest.java index ee94f12297..5e2c8edc07 100644 --- a/src/test/java/rx/observers/TestSubscriberTest.java +++ b/src/test/java/rx/observers/TestSubscriberTest.java @@ -72,7 +72,7 @@ public void testAssertNotMatchValue() { oi.subscribe(o); thrown.expect(AssertionError.class); - thrown.expectMessage("Value at index: 1 expected to be [3] (Integer) but was: [2] (Integer)"); + thrown.expectMessage("Value at index: 1 expected: [3] (Integer) but was: [2] (Integer)"); o.assertReceivedOnNext(Arrays.asList(1, 3)); From 95afbd0f1cc194a6273515f9b81f2a78b885ba32 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Wed, 26 Apr 2017 09:36:45 +0200 Subject: [PATCH 08/37] Release 1.2.10 --- CHANGES.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c0380e97dd..b02e08b281 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,16 @@ # RxJava Releases # +### Version 1.2.10 - April 26, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.10%7C)) + +#### Bugfixes + +- [Pull 5225](https://github.com/ReactiveX/RxJava/pull/5225): Fix `Completable.onErrorResumeNext` unsubscribe not propagated. + +#### Other + +- [Pull 5250](https://github.com/ReactiveX/RxJava/pull/5250): Defer creation of the `TimeoutException` when using the `Single.timeout()` operator. +- [Pull 5258](https://github.com/ReactiveX/RxJava/pull/5258): Use IntelliJ IDE friendly assertion failure message. + ### Version 1.2.9 - March 24, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.9%7C)) #### API enhancements From 8119e786f2f61b1a762588e2edfb0d161a63ad40 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Thu, 27 Apr 2017 17:57:51 +0200 Subject: [PATCH 09/37] 1.x: apply API promotions for 1.3 (#5318) * 1.x: apply API promotions for 1.3 * Fix typos --- src/main/java/rx/BackpressureOverflow.java | 4 +- src/main/java/rx/Completable.java | 13 +- src/main/java/rx/CompletableEmitter.java | 4 +- src/main/java/rx/CompletableSubscriber.java | 4 +- src/main/java/rx/Emitter.java | 4 +- src/main/java/rx/Observable.java | 269 +++++------------- src/main/java/rx/Scheduler.java | 3 +- src/main/java/rx/Single.java | 83 +++--- src/main/java/rx/SingleEmitter.java | 6 +- .../AssemblyStackTraceException.java | 3 +- .../rx/exceptions/CompositeException.java | 4 +- src/main/java/rx/exceptions/Exceptions.java | 13 +- src/main/java/rx/functions/Cancellable.java | 4 +- .../AssertableSubscriberObservable.java | 3 +- .../OnSubscribeFlatMapCompletable.java | 3 +- .../operators/OnSubscribeFlatMapSingle.java | 3 +- .../rx/internal/schedulers/SchedulerWhen.java | 3 +- .../util/BackpressureDrainManager.java | 6 +- .../java/rx/internal/util/BlockingUtils.java | 4 +- .../java/rx/observables/AsyncOnSubscribe.java | 11 +- .../rx/observables/BlockingObservable.java | 19 +- .../java/rx/observables/SyncOnSubscribe.java | 13 +- .../rx/observers/AssertableSubscriber.java | 5 +- .../observers/AsyncCompletableSubscriber.java | 4 +- .../observers/SafeCompletableSubscriber.java | 4 +- .../java/rx/observers/TestSubscriber.java | 10 +- .../RxJavaCompletableExecutionHook.java | 4 +- .../java/rx/plugins/RxJavaErrorHandler.java | 9 +- src/main/java/rx/plugins/RxJavaHooks.java | 3 +- src/main/java/rx/plugins/RxJavaPlugins.java | 11 +- .../java/rx/plugins/RxJavaSchedulersHook.java | 13 +- src/main/java/rx/schedulers/Schedulers.java | 3 +- src/main/java/rx/singles/BlockingSingle.java | 4 +- src/main/java/rx/subjects/UnicastSubject.java | 3 +- 34 files changed, 176 insertions(+), 376 deletions(-) diff --git a/src/main/java/rx/BackpressureOverflow.java b/src/main/java/rx/BackpressureOverflow.java index 39948d8804..6603a6d220 100644 --- a/src/main/java/rx/BackpressureOverflow.java +++ b/src/main/java/rx/BackpressureOverflow.java @@ -15,15 +15,13 @@ */ package rx; -import rx.annotations.Beta; import rx.exceptions.MissingBackpressureException; /** * Generic strategy and default implementations to deal with backpressure buffer overflows. * - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ -@Beta public final class BackpressureOverflow { private BackpressureOverflow() { diff --git a/src/main/java/rx/Completable.java b/src/main/java/rx/Completable.java index 1fb6ad1f49..1ecc1e1993 100644 --- a/src/main/java/rx/Completable.java +++ b/src/main/java/rx/Completable.java @@ -20,7 +20,6 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; -import rx.annotations.*; import rx.exceptions.*; import rx.functions.*; import rx.internal.observers.AssertableSubscriberObservable; @@ -35,10 +34,9 @@ * Represents a deferred computation without any value but only indication for completion or exception. * * The class follows a similar event pattern as Reactive-Streams: onSubscribe (onError|onComplete)? - * - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * + * @since 1.3 */ -@Beta public class Completable { /** The actual subscription action. */ private final OnSubscribe onSubscribe; @@ -539,9 +537,8 @@ public void call(rx.CompletableSubscriber s) { * Completable's protocol are held. * @param producer the callback invoked for each incoming CompletableSubscriber * @return the new Completable instance - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public static Completable fromEmitter(Action1 producer) { return create(new CompletableFromEmitter(producer)); } @@ -2385,10 +2382,10 @@ public void call() { *

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

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

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

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

History: 1.2.7 - experimental * @param the element type * @param emitter the emitter that is called when a Subscriber subscribes to the returned {@code Observable} * @param backpressure the backpressure mode to apply if the downstream Subscriber doesn't request (fast) enough @@ -118,9 +118,8 @@ public static Observable create(OnSubscribe f) { * @see Emitter * @see Emitter.BackpressureMode * @see rx.functions.Cancellable - * @since 1.2.7 - experimental + * @since 1.3 */ - @Experimental public static Observable create(Action1> emitter, Emitter.BackpressureMode backpressure) { return unsafeCreate(new OnSubscribeCreate(emitter, backpressure)); } @@ -148,7 +147,7 @@ public static Observable create(Action1> emitter, Emitter.Back *

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

History: 1.2.7 - experimental * @param * the type of the items that this Observable emits * @param f @@ -157,9 +156,8 @@ public static Observable create(Action1> emitter, Emitter.Back * @return an Observable that, when a {@link Subscriber} subscribes to it, will execute the specified * function * @see ReactiveX operators documentation: Create - * @since 1.2.7 - experimental + * @since 1.3 */ - @Experimental public static Observable unsafeCreate(OnSubscribe f) { return new Observable(RxJavaHooks.onCreate(f)); } @@ -243,9 +241,9 @@ public static Observable create(SyncOnSubscribe syncOnSubscribe) * @see AsyncOnSubscribe#createStateless(Action2) * @see AsyncOnSubscribe#createStateless(Action2, Action0) * @see ReactiveX operators documentation: Create - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 - beta */ - @Experimental + @Beta public static Observable create(AsyncOnSubscribe asyncOnSubscribe) { return unsafeCreate(asyncOnSubscribe); } @@ -350,8 +348,8 @@ public interface Transformer extends Func1, Observable> { * @param the resulting object type * @param converter the function that receives the current Observable instance and returns a value * @return the value returned by the function + * @since 1.3 */ - @Experimental public final R to(Func1, R> converter) { return converter.call(this); } @@ -402,10 +400,8 @@ public Single toSingle() { * calls onCompleted * @see ReactiveX documentation: * Completable - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical - * with the release number) + * @since 1.3 */ - @Beta public Completable toCompletable() { return Completable.fromObservable(this); } @@ -1494,10 +1490,9 @@ public static Observable concat(Observable t1, Observable the common element base type * @param sources the Observable sequence of Observables * @return the new Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ @SuppressWarnings({ "rawtypes", "unchecked" }) - @Beta public static Observable concatDelayError(Observable> sources) { return sources.concatMapDelayError((Func1)UtilityFunctions.identity()); } @@ -1519,9 +1514,8 @@ public static Observable concatDelayError(Observable the common element base type * @param sources the Iterable sequence of Observables * @return the new Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Iterable> sources) { return concatDelayError(from(sources)); } @@ -1546,9 +1540,8 @@ public static Observable concatDelayError(Iterable Observable concatDelayError(Observable t1, Observable t2) { return concatDelayError(just(t1, t2)); } @@ -1575,9 +1568,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t3 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2,Observable t3 ) { return concatDelayError(just(t1, t2, t3)); } @@ -1606,9 +1598,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t4 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4) { return concatDelayError(just(t1, t2, t3, t4)); } @@ -1639,9 +1630,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t5 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5) { return concatDelayError(just(t1, t2, t3, t4, t5)); } @@ -1674,9 +1664,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t6 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6) { return concatDelayError(just(t1, t2, t3, t4, t5, t6)); } @@ -1711,9 +1700,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t7 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7) { return concatDelayError(just(t1, t2, t3, t4, t5, t6, t7)); } @@ -1750,9 +1738,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t8 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8) { return concatDelayError(just(t1, t2, t3, t4, t5, t6, t7, t8)); } @@ -1791,9 +1778,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t9 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8, Observable t9) { return concatDelayError(just(t1, t2, t3, t4, t5, t6, t7, t8, t9)); } @@ -2026,54 +2012,6 @@ public static Observable from(T[] array) { return unsafeCreate(new OnSubscribeFromArray(array)); } - /** - * Provides an API (via a cold Observable) that bridges the reactive world with the callback-style, - * generally non-backpressured world. - *

- * Example: - *


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

- * You should call the Emitter's onNext, onError and onCompleted methods in a serialized fashion. The - * rest of its methods are thread-safe. - * - * @param the element type - * @param emitter the emitter that is called when a Subscriber subscribes to the returned {@code Observable} - * @param backpressure the backpressure mode to apply if the downstream Subscriber doesn't request (fast) enough - * @return the new Observable instance - * @see Emitter - * @see Emitter.BackpressureMode - * @see rx.functions.Cancellable - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) - * @deprecated 1.2.7 - aliased to {@link #create(Action1, rx.Emitter.BackpressureMode)}, will be removed in 1.3.0 - */ - @Experimental - @Deprecated - public static Observable fromEmitter(Action1> emitter, Emitter.BackpressureMode backpressure) { - return unsafeCreate(new OnSubscribeCreate(emitter, backpressure)); - } - /** * Returns an Observable that, when an observer subscribes to it, invokes a function you specify and then * emits the value returned from that function. @@ -3068,9 +3006,8 @@ public static Observable mergeDelayError(ObservableReactiveX operators documentation: Merge - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable mergeDelayError(Observable> source, int maxConcurrent) { return source.lift(OperatorMerge.instance(true, maxConcurrent)); } @@ -3684,10 +3621,8 @@ public static Observable switchOnNext(ObservableReactiveX operators documentation: Switch - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable switchOnNextDelayError(Observable> sequenceOfSequences) { return sequenceOfSequences.lift(OperatorSwitch.instance(true)); } @@ -3861,10 +3796,8 @@ public static Observable using( * a terminal event ({@code onComplete} or {@code onError}). * @return the Observable whose lifetime controls the lifetime of the dependent resource object * @see ReactiveX operators documentation: Using - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable using( final Func0 resourceFactory, final Func1> observableFactory, @@ -3966,8 +3899,8 @@ public static Observable zip(Iterable> ws, FuncN< * an item that will be emitted by the resulting Observable * @return an Observable that emits the zipped results * @see ReactiveX operators documentation: Zip + * @since 1.3 */ - @Experimental public static Observable zip(Observable[] ws, FuncN zipFunction) { return Observable.just(ws).lift(new OperatorZip(zipFunction)); } @@ -5192,9 +5125,8 @@ public final Observable concatMap(Func1 the result value type * @param func the function that maps the items of this Observable into the inner Observables. * @return the new Observable instance with the concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Observable concatMapDelayError(Func1> func) { if (this instanceof ScalarSynchronousObservable) { ScalarSynchronousObservable scalar = (ScalarSynchronousObservable) this; @@ -5692,9 +5624,8 @@ public final Observable delaySubscription(Func0> * to this Observable. * @return an Observable that delays the subscription to this Observable * until the other Observable emits an element or completes normally. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Observable delaySubscription(Observable other) { if (other == null) { throw new NullPointerException(); @@ -5836,10 +5767,8 @@ public final Observable distinctUntilChanged(Func1ReactiveX operators documentation: Distinct - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical - * with the release number) + * @since 1.3 */ - @Beta public final Observable distinctUntilChanged(Func2 comparator) { return lift(new OperatorDistinctUntilChanged(comparator)); } @@ -6098,9 +6027,8 @@ public final Observable doOnUnsubscribe(final Action0 unsubscribe) { * @param o1 the first source * @param o2 the second source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager(Observable o1, Observable o2) { return concatEager(Arrays.asList(o1, o2)); @@ -6124,9 +6052,8 @@ public static Observable concatEager(Observable o1, Observab * @param o2 the second source * @param o3 the third source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6154,9 +6081,8 @@ public static Observable concatEager( * @param o3 the third source * @param o4 the fourth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6185,9 +6111,8 @@ public static Observable concatEager( * @param o4 the fourth source * @param o5 the fifth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6218,9 +6143,8 @@ public static Observable concatEager( * @param o5 the fifth source * @param o6 the sixth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6252,9 +6176,8 @@ public static Observable concatEager( * @param o6 the sixth source * @param o7 the seventh source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6288,9 +6211,8 @@ public static Observable concatEager( * @param o7 the seventh source * @param o8 the eighth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6325,9 +6247,8 @@ public static Observable concatEager( * @param o8 the eighth source * @param o9 the ninth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6355,9 +6276,8 @@ public static Observable concatEager( * @param the value type * @param sources a sequence of Observables that need to be eagerly concatenated * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings({ "unchecked", "rawtypes" }) public static Observable concatEager(Iterable> sources) { return Observable.from(sources).concatMapEager((Func1)UtilityFunctions.identity()); @@ -6380,9 +6300,8 @@ public static Observable concatEager(Iterable Observable concatEager(Iterable> sources, int capacityHint) { return Observable.from(sources).concatMapEager((Func1)UtilityFunctions.identity(), capacityHint); @@ -6404,9 +6323,8 @@ public static Observable concatEager(Iterable the value type * @param sources a sequence of Observables that need to be eagerly concatenated * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings({ "unchecked", "rawtypes" }) public static Observable concatEager(Observable> sources) { return sources.concatMapEager((Func1)UtilityFunctions.identity()); @@ -6429,9 +6347,8 @@ public static Observable concatEager(Observable Observable concatEager(Observable> sources, int capacityHint) { return sources.concatMapEager((Func1)UtilityFunctions.identity(), capacityHint); @@ -6455,9 +6372,8 @@ public static Observable concatEager(Observable Observable concatMapEager(Func1> mapper) { return concatMapEager(mapper, RxRingBuffer.SIZE); } @@ -6481,9 +6397,8 @@ public final Observable concatMapEager(Func1 Observable concatMapEager(Func1> mapper, int capacityHint) { if (capacityHint < 1) { throw new IllegalArgumentException("capacityHint > 0 required but it was " + capacityHint); @@ -6511,9 +6426,8 @@ public final Observable concatMapEager(Func1 Observable concatMapEager(Func1> mapper, int capacityHint, int maxConcurrent) { if (capacityHint < 1) { throw new IllegalArgumentException("capacityHint > 0 required but it was " + capacityHint); @@ -6983,13 +6897,13 @@ public final Observable flatMap(final Func1Scheduler: *

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

History: 1.2.7 - experimental * @param mapper the function that receives an upstream value and turns it into a Completable * to be merged. * @return the new Observable instance * @see #flatMapCompletable(Func1, boolean, int) - * @since 1.2.7 - experimental + * @since 1.3 */ - @Experimental public final Observable flatMapCompletable(Func1 mapper) { return flatMapCompletable(mapper, false, Integer.MAX_VALUE); } @@ -7004,15 +6918,15 @@ public final Observable flatMapCompletable(Func1Scheduler: *

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

History: 1.2.7 - experimental * @param mapper the function that receives an upstream value and turns it into a Completable * to be merged. * @param delayErrors if true, errors from the upstream and from the inner Completables get delayed till * the all of them terminate. * @return the new Observable instance - * @since 1.2.7 - experimental + * @since 1.3 * @see #flatMapCompletable(Func1, boolean, int) */ - @Experimental public final Observable flatMapCompletable(Func1 mapper, boolean delayErrors) { return flatMapCompletable(mapper, delayErrors, Integer.MAX_VALUE); } @@ -7028,15 +6942,15 @@ public final Observable flatMapCompletable(Func1Scheduler: *

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

History: 1.2.7 - experimental * @param mapper the function that receives an upstream value and turns it into a Completable * to be merged. * @param delayErrors if true, errors from the upstream and from the inner Completables get delayed till * the all of them terminate. * @param maxConcurrency the maximum number of inner Completables to run at a time * @return the new Observable instance - * @since 1.2.7 - experimental + * @since 1.3 */ - @Experimental public final Observable flatMapCompletable(Func1 mapper, boolean delayErrors, int maxConcurrency) { return unsafeCreate(new OnSubscribeFlatMapCompletable(this, mapper, delayErrors, maxConcurrency)); } @@ -7183,14 +7097,14 @@ public final Observable flatMapIterable(Func1Scheduler: *

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

History: 1.2.7 - experimental * @param the value type of the inner Singles and the resulting Observable * @param mapper the function that receives an upstream value and turns it into a Single * to be merged. * @return the new Observable instance * @see #flatMapSingle(Func1, boolean, int) - * @since 1.2.7 - experimental + * @since 1.3 */ - @Experimental public final Observable flatMapSingle(Func1> mapper) { return flatMapSingle(mapper, false, Integer.MAX_VALUE); } @@ -7204,16 +7118,16 @@ public final Observable flatMapSingle(Func1Scheduler: *

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

History: 1.2.7 - experimental * @param the value type of the inner Singles and the resulting Observable * @param mapper the function that receives an upstream value and turns it into a Single * to be merged. * @param delayErrors if true, errors from the upstream and from the inner Singles get delayed till * the all of them terminate. * @return the new Observable instance - * @since 1.2.7 - experimental + * @since 1.3 * @see #flatMapSingle(Func1, boolean, int) */ - @Experimental public final Observable flatMapSingle(Func1> mapper, boolean delayErrors) { return flatMapSingle(mapper, delayErrors, Integer.MAX_VALUE); } @@ -7228,6 +7142,7 @@ public final Observable flatMapSingle(Func1Scheduler: *

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

History: 1.2.7 - experimental * @param the value type of the inner Singles and the resulting Observable * @param mapper the function that receives an upstream value and turns it into a Single * to be merged. @@ -7235,9 +7150,8 @@ public final Observable flatMapSingle(Func1 Observable flatMapSingle(Func1> mapper, boolean delayErrors, int maxConcurrency) { return unsafeCreate(new OnSubscribeFlatMapSingle(this, mapper, delayErrors, maxConcurrency)); } @@ -7419,8 +7333,8 @@ public final Observable> groupBy(final Func1ReactiveX operators documentation: GroupBy + * @since 1.3 */ - @Experimental public final Observable> groupBy(final Func1 keySelector, final Func1 elementSelector, final Func1, Map> evictingMapFactory) { if (evictingMapFactory == null) { @@ -8049,9 +7963,8 @@ public final Observable onBackpressureBuffer(long capacity, Action0 onOverflo * @param overflowStrategy how should the {@code Observable} react to buffer overflows. Null is not allowed. * @return the source {@code Observable} modified to buffer items up to the given capacity * @see ReactiveX operators documentation: backpressure operators - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Observable onBackpressureBuffer(long capacity, Action0 onOverflow, BackpressureOverflow.Strategy overflowStrategy) { return lift(new OperatorOnBackpressureBuffer(capacity, onOverflow, overflowStrategy)); } @@ -8075,7 +7988,6 @@ public final Observable onBackpressureBuffer(long capacity, Action0 onOverflo * @param onDrop the action to invoke for each item dropped. onDrop action should be fast and should never block. * @return the source Observable modified to drop {@code onNext} notifications on overflow * @see ReactiveX operators documentation: backpressure operators - * @Experimental The behavior of this can change at any time. * @since 1.1.0 */ public final Observable onBackpressureDrop(Action1 onDrop) { @@ -8306,9 +8218,8 @@ public final Observable onExceptionResumeNext(final Observable r * * @return an Observable which out references to the upstream producer and downstream Subscriber if * the sequence is terminated or downstream unsubscribes - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable onTerminateDetach() { return unsafeCreate(new OnSubscribeDetach(this)); } @@ -8381,9 +8292,8 @@ public final Observable publish(Func1, ? extends Ob * * @param n the initial request amount, further request will happen after 75% of this value * @return the Observable that rebatches request amounts from downstream - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable rebatchRequests(int n) { if (n <= 0) { throw new IllegalArgumentException("n > 0 required but it was " + n); @@ -10511,7 +10421,7 @@ public final Observable subscribeOn(Scheduler scheduler) { *

Scheduler:
*
you specify which {@link Scheduler} this operator will use
* - * + *

History: 1.2.7 - experimental * @param scheduler * the {@link Scheduler} to perform subscription actions on * @param requestOn if true, requests are rerouted to the given Scheduler as well (strong pipelining) @@ -10523,9 +10433,8 @@ public final Observable subscribeOn(Scheduler scheduler) { * @see RxJava Threading Examples * @see #observeOn * @see #subscribeOn(Scheduler) - * @since 1.2.7 - experimental + * @since 1.3 */ - @Experimental public final Observable subscribeOn(Scheduler scheduler, boolean requestOn) { if (this instanceof ScalarSynchronousObservable) { return ((ScalarSynchronousObservable)this).scalarScheduleOn(scheduler); @@ -10589,10 +10498,8 @@ public final Observable switchMap(Func1ReactiveX operators documentation: FlatMap - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Observable switchMapDelayError(Func1> func) { return switchOnNextDelayError(map(func)); } @@ -11929,9 +11836,8 @@ public final Observable> toSortedList(Func2ReactiveX operators documentation: To - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Observable> toSortedList(int initialCapacity) { return lift(new OperatorToObservableSortedList(initialCapacity)); } @@ -11957,9 +11863,8 @@ public final Observable> toSortedList(int initialCapacity) { * @return an Observable that emits a list that contains the items emitted by the source Observable in * sorted order * @see ReactiveX operators documentation: To - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Observable> toSortedList(Func2 sortFunction, int initialCapacity) { return lift(new OperatorToObservableSortedList(sortFunction, initialCapacity)); } @@ -11984,8 +11889,8 @@ public final Observable> toSortedList(Func2 sorted() { return toSortedList().flatMapIterable(UtilityFunctions.>identity()); } @@ -12009,8 +11914,8 @@ public final Observable sorted() { * a function that compares two items emitted by the source Observable and returns an Integer * that indicates their sort order * @return an Observable that emits the items emitted by the source Observable in sorted order + * @since 1.3 */ - @Experimental public final Observable sorted(Func2 sortFunction) { return toSortedList(sortFunction).flatMapIterable(UtilityFunctions.>identity()); } @@ -12061,11 +11966,9 @@ public final Observable unsubscribeOn(Scheduler scheduler) { * @return an Observable that merges the specified Observable into this Observable by using the * {@code resultSelector} function only when the source Observable sequence (this instance) emits an * item - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 * @see ReactiveX operators documentation: CombineLatest */ - @Experimental public final Observable withLatestFrom(Observable other, Func2 resultSelector) { return lift(new OperatorWithLatestFrom(other, resultSelector)); } @@ -12094,10 +11997,8 @@ public final Observable withLatestFrom(Observable other, * @param o2 the second other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom(Observable o1, Observable o2, Func3 combiner) { return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2 }, null, Functions.fromFunc(combiner))); } @@ -12128,10 +12029,8 @@ public final Observable withLatestFrom(Observable o1, Observa * @param o3 the third other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( Observable o1, Observable o2, Observable o3, @@ -12168,10 +12067,8 @@ public final Observable withLatestFrom( * @param o4 the fourth other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( Observable o1, Observable o2, Observable o3, Observable o4, @@ -12209,10 +12106,8 @@ public final Observable withLatestFrom( * @param o5 the fifth other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( Observable o1, Observable o2, Observable o3, Observable o4, @@ -12254,10 +12149,8 @@ public final Observable withLatestFrom( * @param o6 the sixth other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( Observable o1, Observable o2, Observable o3, Observable o4, @@ -12301,10 +12194,8 @@ public final Observable withLatestFrom( * @param o7 the seventh other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( Observable o1, Observable o2, Observable o3, Observable o4, @@ -12351,10 +12242,8 @@ public final Observable withLatestFrom( * @param o8 the eighth other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( Observable o1, Observable o2, Observable o3, Observable o4, @@ -12386,10 +12275,8 @@ public final Observable withLatestFrom( * @param others the array of other sources * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom(Observable[] others, FuncN combiner) { return unsafeCreate(new OperatorWithLatestFromMany(this, others, null, combiner)); } @@ -12415,10 +12302,8 @@ public final Observable withLatestFrom(Observable[] others, FuncN c * @param others the iterable of other sources * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom(Iterable> others, FuncN combiner) { return unsafeCreate(new OperatorWithLatestFromMany(this, null, others, combiner)); } @@ -12899,10 +12784,10 @@ public final Observable zipWith(Observable other, Func2 *

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

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

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

History: 1.2.3 - experimental * @return the new AssertableSubscriber instance * @param initialRequestAmount the amount to request from upstream upfront, non-negative (not verified) - * @since 1.2.3 + * @since 1.3 */ - @Experimental public final AssertableSubscriber test(long initialRequestAmount) { AssertableSubscriber ts = AssertableSubscriberObservable.create(initialRequestAmount); subscribe(ts); diff --git a/src/main/java/rx/Scheduler.java b/src/main/java/rx/Scheduler.java index b98615ff47..89259b178b 100644 --- a/src/main/java/rx/Scheduler.java +++ b/src/main/java/rx/Scheduler.java @@ -17,7 +17,6 @@ import java.util.concurrent.TimeUnit; -import rx.annotations.Experimental; import rx.functions.*; import rx.internal.schedulers.*; import rx.schedulers.Schedulers; @@ -203,9 +202,9 @@ public long now() { * @param combine the function that takes a two-level nested Observable sequence of a Completable and returns * the Completable that will be subscribed to and should trigger the execution of the scheduled Actions. * @return the Scheduler with the customized execution behavior + * @since 1.3 */ @SuppressWarnings("unchecked") - @Experimental public S when(Func1>, Completable> combine) { return (S) new SchedulerWhen(combine, this); } diff --git a/src/main/java/rx/Single.java b/src/main/java/rx/Single.java index 0b39b6f24f..a9bff5e388 100644 --- a/src/main/java/rx/Single.java +++ b/src/main/java/rx/Single.java @@ -145,8 +145,8 @@ public interface OnSubscribe extends Action1> { * the Operator that implements the Single-operating function to be applied to the source Single * @return a Single that is the result of applying the lifted Operator to the source Single * @see RxJava wiki: Implementing Your Own Operators + * @since 1.3 */ - @Beta public final Single lift(final Operator lift) { return create(new SingleLiftObservableOperator(this.onSubscribe, lift)); } @@ -602,12 +602,12 @@ public static Single fromCallable(final Callable func) { * *

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

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

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

History: 1.2.7 - experimental * @param the value type of the inner Singles and the resulting Observable * @param sources the Observable that emits Singles to be merged * @return the new Observable instance * @see #merge(Observable, int) * @see #mergeDelayError(Observable) * @see #mergeDelayError(Observable, int) - * @since 1.2.7 - experimental + * @since 1.3 */ - @Experimental public static Observable merge(Observable> sources) { return merge(sources, Integer.MAX_VALUE); } @@ -963,14 +963,14 @@ public static Observable merge(Observable> *

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

History: 1.2.7 - experimental * @param the value type of the inner Singles and the resulting Observable * @param sources the Observable that emits Singles to be merged * @param maxConcurrency the maximum number of inner Singles to run at a time * @return the new Observable instance - * @since 1.2.7 - experimental + * @since 1.3 */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Experimental public static Observable merge(Observable> sources, int maxConcurrency) { return sources.flatMapSingle((Func1)UtilityFunctions.identity(), false, maxConcurrency); } @@ -985,15 +985,15 @@ public static Observable merge(Observable> *

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

History: 1.2.7 - experimental * @param the value type of the inner Singles and the resulting Observable * @param sources the Observable that emits Singles to be merged * @return the new Observable instance * @see #mergeDelayError(Observable, int) * @see #merge(Observable) * @see #merge(Observable, int) - * @since 1.2.7 - experimental + * @since 1.3 */ - @Experimental public static Observable mergeDelayError(Observable> sources) { return merge(sources, Integer.MAX_VALUE); } @@ -1009,14 +1009,14 @@ public static Observable mergeDelayError(ObservableScheduler: *

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

History: 1.2.7 - experimental * @param the value type of the inner Singles and the resulting Observable * @param sources the Observable that emits Singles to be merged * @param maxConcurrency the maximum number of inner Singles to run at a time * @return the new Observable instance - * @since 1.2.7 - experimental + * @since 1.3 */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Experimental public static Observable mergeDelayError(Observable> sources, int maxConcurrency) { return sources.flatMapSingle((Func1)UtilityFunctions.identity(), true, maxConcurrency); } @@ -1452,8 +1452,8 @@ public static Single zip(Iterable> singles, FuncNReactiveX operators documentation: Replay + * @since 1.3 */ - @Experimental public final Single cache() { return toObservable().cacheWithInitialCapacity(1).toSingle(); } @@ -1537,9 +1537,8 @@ public final Observable flatMapObservable(Func1ReactiveX operators documentation: FlatMap - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Completable flatMapCompletable(final Func1 func) { return Completable.create(new CompletableFlatMapSingleToCompletable(this, func)); } @@ -1669,10 +1668,8 @@ public final Single onErrorReturn(Func1 resumeFunctio * @param resumeSingleInCaseOfError a Single that will take control if source Single encounters an error. * @return the original Single, with appropriately modified behavior. * @see ReactiveX operators documentation: Catch - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single onErrorResumeNext(Single resumeSingleInCaseOfError) { return new Single(SingleOperatorOnErrorResumeNext.withOther(this, resumeSingleInCaseOfError)); } @@ -1703,10 +1700,8 @@ public final Single onErrorResumeNext(Single resumeSingleInCaseO * @param resumeFunctionInCaseOfError a function that returns a Single that will take control if source Single encounters an error. * @return the original Single, with appropriately modified behavior. * @see ReactiveX operators documentation: Catch - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single onErrorResumeNext(final Func1> resumeFunctionInCaseOfError) { return new Single(SingleOperatorOnErrorResumeNext.withFunction(this, resumeFunctionInCaseOfError)); } @@ -2117,8 +2112,8 @@ public final Single takeUntil(final Single other) { * @param the resulting object type * @param converter the function that receives the current Single instance and returns a value * @return the value returned by the function + * @since 1.3 */ - @Experimental public final R to(Func1, R> converter) { return converter.call(this); } @@ -2150,10 +2145,8 @@ public final Observable toObservable() { * @return a {@link Completable} that calls {@code onCompleted} on it's subscriber when the source {@link Single} * calls {@code onSuccess}. * @see ReactiveX documentation: Completable - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical - * with the release number). + * @since 1.3 */ - @Beta public final Completable toCompletable() { return Completable.fromSingle(this); } @@ -2276,8 +2269,8 @@ public Single call() { * * @return a {@code BlockingSingle} version of this Single. * @see ReactiveX operators documentation: To + * @since 1.3 */ - @Beta public final BlockingSingle toBlocking() { return BlockingSingle.from(this); } @@ -2326,9 +2319,8 @@ public final Single zipWith(Single other, Func2ReactiveX operators documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single doOnError(final Action1 onError) { if (onError == null) { throw new IllegalArgumentException("onError is null"); @@ -2355,9 +2347,8 @@ public void call(final Throwable throwable) { * the action to invoke when the source {@link Single} calls {@code onSuccess} or {@code onError}. * @return the source {@link Single} with the side-effecting behavior applied * @see ReactiveX operators documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Single doOnEach(final Action1> onNotification) { if (onNotification == null) { throw new IllegalArgumentException("onNotification is null"); @@ -2389,9 +2380,8 @@ public void call(final Throwable throwable) { * the action to invoke when the source {@link Single} calls {@code onSuccess} * @return the source {@link Single} with the side-effecting behavior applied * @see ReactiveX operators documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Single doOnSuccess(final Action1 onSuccess) { if (onSuccess == null) { throw new IllegalArgumentException("onSuccess is null"); @@ -2417,9 +2407,8 @@ public final Single doOnSuccess(final Action1 onSuccess) { * the action that gets called when an observer subscribes to this {@code Single} * @return the source {@code Single} modified so as to call this Action when appropriate * @see ReactiveX operators documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single doOnSubscribe(final Action0 subscribe) { return create(new SingleDoOnSubscribe(onSubscribe, subscribe)); } @@ -2442,9 +2431,8 @@ public final Single doOnSubscribe(final Action0 subscribe) { * the {@link Scheduler} to use for delaying * @return the source Single shifted in time by the specified delay * @see ReactiveX operators documentation: Delay - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single delay(long delay, TimeUnit unit, Scheduler scheduler) { return create(new SingleDelay(onSubscribe, delay, unit, scheduler)); } @@ -2465,9 +2453,8 @@ public final Single delay(long delay, TimeUnit unit, Scheduler scheduler) { * the {@link TimeUnit} in which {@code period} is defined * @return the source Single shifted in time by the specified delay * @see ReactiveX operators documentation: Delay - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single delay(long delay, TimeUnit unit) { return delay(delay, unit, Schedulers.computation()); } @@ -2495,9 +2482,8 @@ public final Single delay(long delay, TimeUnit unit) { * @return a {@link Single} whose {@link Observer}s' subscriptions trigger an invocation of the given * {@link Single} factory function. * @see ReactiveX operators documentation: Defer - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Single defer(final Callable> singleFactory) { return create(new OnSubscribe() { @Override @@ -2531,9 +2517,8 @@ public void call(SingleSubscriber singleSubscriber) { * the action that gets called when this {@link Single} is unsubscribed. * @return the source {@link Single} modified so as to call this Action when appropriate. * @see ReactiveX operators documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single doOnUnsubscribe(final Action0 action) { return create(new SingleDoOnUnsubscribe(onSubscribe, action)); } @@ -2553,9 +2538,8 @@ public final Single doOnUnsubscribe(final Action0 action) { * @return a {@link Single} that emits the same item or error as the source {@link Single}, then invokes the * {@link Action0} * @see ReactiveX operators documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single doAfterTerminate(Action0 action) { return create(new SingleDoAfterTerminate(this, action)); } @@ -2735,8 +2719,8 @@ public final Single retryWhen(final Func1, ? * the function that will dispose of the resource * @return the Single whose lifetime controls the lifetime of the dependent resource object * @see ReactiveX operators documentation: Using + * @since 1.3 */ - @Beta public static Single using( final Func0 resourceFactory, final Func1> singleFactory, @@ -2770,10 +2754,8 @@ public static Single using( * a terminal event ({@code onComplete} or {@code onError}). * @return the Single whose lifetime controls the lifetime of the dependent resource object * @see ReactiveX operators documentation: Using - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Single using( final Func0 resourceFactory, final Func1> singleFactory, @@ -2806,9 +2788,8 @@ public static Single using( * to this Single. * @return a Single that delays the subscription to this Single * until the Observable emits an element or completes normally. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single delaySubscription(Observable other) { if (other == null) { throw new NullPointerException(); @@ -2874,10 +2855,10 @@ public void call() { *

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

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

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

History: 1.2.3 - experimental * @param the success value type + * @since 1.3 */ -@Experimental public interface SingleEmitter { /** diff --git a/src/main/java/rx/exceptions/AssemblyStackTraceException.java b/src/main/java/rx/exceptions/AssemblyStackTraceException.java index 8342adc637..c21cb94510 100644 --- a/src/main/java/rx/exceptions/AssemblyStackTraceException.java +++ b/src/main/java/rx/exceptions/AssemblyStackTraceException.java @@ -17,14 +17,13 @@ import java.util.*; -import rx.annotations.Experimental; import rx.plugins.RxJavaHooks; /** * A RuntimeException that is stackless but holds onto a textual * stacktrace from tracking the assembly location of operators. + * @since 1.3 */ -@Experimental public final class AssemblyStackTraceException extends RuntimeException { /** */ diff --git a/src/main/java/rx/exceptions/CompositeException.java b/src/main/java/rx/exceptions/CompositeException.java index cfbfdc2113..855d720e49 100644 --- a/src/main/java/rx/exceptions/CompositeException.java +++ b/src/main/java/rx/exceptions/CompositeException.java @@ -18,8 +18,6 @@ import java.io.*; import java.util.*; -import rx.annotations.Beta; - /** * Represents an exception that is a composite of one or more other exceptions. A {@code CompositeException} * does not modify the structure of any exception it wraps, but at print-time it iterates through the list of @@ -85,8 +83,8 @@ public CompositeException(Collection errors) { /** * Constructs a CompositeException instance with the supplied initial Throwables. * @param errors the array of Throwables + * @since 1.3 */ - @Beta public CompositeException(Throwable... errors) { Set deDupedExceptions = new LinkedHashSet(); List localExceptions = new ArrayList(); diff --git a/src/main/java/rx/exceptions/Exceptions.java b/src/main/java/rx/exceptions/Exceptions.java index 276e563e48..9e5f25393d 100644 --- a/src/main/java/rx/exceptions/Exceptions.java +++ b/src/main/java/rx/exceptions/Exceptions.java @@ -19,7 +19,6 @@ import rx.Observer; import rx.SingleSubscriber; -import rx.annotations.Beta; /** * Utility class with methods to wrap checked exceptions and @@ -182,9 +181,8 @@ public static void throwIfAny(List exceptions) { * @param t the exception * @param o the observer to report to * @param value the value that caused the exception - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static void throwOrReport(Throwable t, Observer o, Object value) { Exceptions.throwIfFatal(t); o.onError(OnErrorThrowable.addValueAsLastCause(t, value)); @@ -196,9 +194,8 @@ public static void throwOrReport(Throwable t, Observer o, Object value) { * @param t the exception * @param o the observer to report to * @param value the value that caused the exception - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static void throwOrReport(Throwable t, SingleSubscriber o, Object value) { Exceptions.throwIfFatal(t); o.onError(OnErrorThrowable.addValueAsLastCause(t, value)); @@ -208,9 +205,8 @@ public static void throwOrReport(Throwable t, SingleSubscriber o, Object valu * Forwards a fatal exception or reports it to the given Observer. * @param t the exception * @param o the observer to report to - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static void throwOrReport(Throwable t, Observer o) { Exceptions.throwIfFatal(t); o.onError(t); @@ -221,9 +217,8 @@ public static void throwOrReport(Throwable t, Observer o) { * * @param throwable the exception. * @param subscriber the subscriber to report to. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number). + * @since 1.3 */ - @Beta public static void throwOrReport(Throwable throwable, SingleSubscriber subscriber) { Exceptions.throwIfFatal(throwable); subscriber.onError(throwable); diff --git a/src/main/java/rx/functions/Cancellable.java b/src/main/java/rx/functions/Cancellable.java index 1109522d86..7b92b71884 100644 --- a/src/main/java/rx/functions/Cancellable.java +++ b/src/main/java/rx/functions/Cancellable.java @@ -16,12 +16,10 @@ package rx.functions; -import rx.annotations.Experimental; - /** * A functional interface that has a single close method that can throw. + * @since 1.3 */ -@Experimental public interface Cancellable { /** diff --git a/src/main/java/rx/internal/observers/AssertableSubscriberObservable.java b/src/main/java/rx/internal/observers/AssertableSubscriberObservable.java index b9068b04ff..d9081b5a8d 100644 --- a/src/main/java/rx/internal/observers/AssertableSubscriberObservable.java +++ b/src/main/java/rx/internal/observers/AssertableSubscriberObservable.java @@ -20,7 +20,6 @@ import rx.Producer; import rx.Subscriber; -import rx.annotations.Experimental; import rx.functions.Action0; import rx.observers.TestSubscriber; import rx.observers.AssertableSubscriber; @@ -33,8 +32,8 @@ * * @param * the value type + * @since 1.3 */ -@Experimental public class AssertableSubscriberObservable extends Subscriber implements AssertableSubscriber { private final TestSubscriber ts; diff --git a/src/main/java/rx/internal/operators/OnSubscribeFlatMapCompletable.java b/src/main/java/rx/internal/operators/OnSubscribeFlatMapCompletable.java index bfb8d6d69a..8b22e740a0 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeFlatMapCompletable.java +++ b/src/main/java/rx/internal/operators/OnSubscribeFlatMapCompletable.java @@ -27,8 +27,9 @@ /** * Maps upstream values to Completables and merges them, up to a given * number of them concurrently, optionally delaying errors. + *

History: 1.2.7 - experimental * @param the upstream value type - * @since 1.2.7 - experimental + * @since 1.3 */ public final class OnSubscribeFlatMapCompletable implements Observable.OnSubscribe { diff --git a/src/main/java/rx/internal/operators/OnSubscribeFlatMapSingle.java b/src/main/java/rx/internal/operators/OnSubscribeFlatMapSingle.java index b64a4ef760..190d6acbc0 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeFlatMapSingle.java +++ b/src/main/java/rx/internal/operators/OnSubscribeFlatMapSingle.java @@ -31,9 +31,10 @@ /** * Maps upstream values to Singles and merges them, up to a given * number of them concurrently, optionally delaying errors. + *

History: 1.2.7 - experimental * @param the upstream value type * @param the inner Singles and result value type - * @since 1.2.7 - experimental + * @since 1.3 */ public final class OnSubscribeFlatMapSingle implements Observable.OnSubscribe { diff --git a/src/main/java/rx/internal/schedulers/SchedulerWhen.java b/src/main/java/rx/internal/schedulers/SchedulerWhen.java index 880992c9d5..f35528bb12 100644 --- a/src/main/java/rx/internal/schedulers/SchedulerWhen.java +++ b/src/main/java/rx/internal/schedulers/SchedulerWhen.java @@ -26,7 +26,6 @@ import rx.Observer; import rx.Scheduler; import rx.Subscription; -import rx.annotations.Experimental; import rx.functions.Action0; import rx.functions.Func1; import rx.internal.operators.BufferUntilSubscriber; @@ -101,8 +100,8 @@ * })); * }); * + * @since 1.3 */ -@Experimental public class SchedulerWhen extends Scheduler implements Subscription { private final Scheduler actualScheduler; private final Observer> workerObserver; diff --git a/src/main/java/rx/internal/util/BackpressureDrainManager.java b/src/main/java/rx/internal/util/BackpressureDrainManager.java index 9fde1e62f7..da31c78178 100644 --- a/src/main/java/rx/internal/util/BackpressureDrainManager.java +++ b/src/main/java/rx/internal/util/BackpressureDrainManager.java @@ -18,16 +18,14 @@ import java.util.concurrent.atomic.AtomicLong; import rx.Producer; -import rx.annotations.Experimental; /** * Manages the producer-backpressure-consumer interplay by * matching up available elements with requested elements and/or * terminal events. - * - * @since 1.1.0 + *

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

* Not intended to be part of the public API. + * @since 1.3 */ -@Experimental public final class BlockingUtils { private BlockingUtils() { } @@ -37,7 +36,6 @@ private BlockingUtils() { } * @param latch a CountDownLatch * @param subscription the Subscription to wait on. */ - @Experimental public static void awaitForComplete(CountDownLatch latch, Subscription subscription) { if (latch.getCount() == 0) { // Synchronous observable completes before awaiting for it. diff --git a/src/main/java/rx/observables/AsyncOnSubscribe.java b/src/main/java/rx/observables/AsyncOnSubscribe.java index e3acb1627e..e8d1ff71e6 100644 --- a/src/main/java/rx/observables/AsyncOnSubscribe.java +++ b/src/main/java/rx/observables/AsyncOnSubscribe.java @@ -23,7 +23,7 @@ import rx.Observable; import rx.Observable.OnSubscribe; import rx.Observer; -import rx.annotations.Experimental; +import rx.annotations.Beta; import rx.functions.*; import rx.internal.operators.BufferUntilSubscriber; import rx.observers.SerializedObserver; @@ -43,8 +43,9 @@ * {@link #onUnsubscribe(Object) onUnsubscribe(S)}. * @param * the type of {@code Subscribers} that will be compatible with {@code this}. + * @since 1.3 - beta */ -@Experimental +@Beta public abstract class AsyncOnSubscribe implements OnSubscribe { /** @@ -110,7 +111,6 @@ protected void onUnsubscribe(S state) { * {@link #next(Object, long, Observer) next(S, long, Observer)}) * @return an AsyncOnSubscribe that emits data in a protocol compatible with back-pressure. */ - @Experimental public static AsyncOnSubscribe createSingleState(Func0 generator, final Action3>> next) { Func3>, S> nextFunc = @@ -141,7 +141,6 @@ public S call(S state, Long requested, Observer> subscri * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental public static AsyncOnSubscribe createSingleState(Func0 generator, final Action3>> next, final Action1 onUnsubscribe) { @@ -171,7 +170,6 @@ public S call(S state, Long requested, Observer> subscri * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental public static AsyncOnSubscribe createStateful(Func0 generator, Func3>, ? extends S> next, Action1 onUnsubscribe) { @@ -192,7 +190,6 @@ public static AsyncOnSubscribe createStateful(Func0 ge * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental public static AsyncOnSubscribe createStateful(Func0 generator, Func3>, ? extends S> next) { return new AsyncOnSubscribeImpl(generator, next); @@ -212,7 +209,6 @@ public static AsyncOnSubscribe createStateful(Func0 ge * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental public static AsyncOnSubscribe createStateless(final Action2>> next) { Func3>, Void> nextFunc = new Func3>, Void>() { @@ -240,7 +236,6 @@ public Void call(Void state, Long requested, Observer> s * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental public static AsyncOnSubscribe createStateless(final Action2>> next, final Action0 onUnsubscribe) { Func3>, Void> nextFunc = diff --git a/src/main/java/rx/observables/BlockingObservable.java b/src/main/java/rx/observables/BlockingObservable.java index 59cb245415..ebde4486b0 100644 --- a/src/main/java/rx/observables/BlockingObservable.java +++ b/src/main/java/rx/observables/BlockingObservable.java @@ -22,7 +22,6 @@ import rx.*; import rx.Observable; import rx.Observer; -import rx.annotations.Beta; import rx.exceptions.*; import rx.functions.*; import rx.internal.operators.*; @@ -469,9 +468,8 @@ public void onNext(final T item) { /** * Runs the source observable to a terminal event, ignoring any values and rethrowing any exception. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public void subscribe() { final CountDownLatch cdl = new CountDownLatch(1); final Throwable[] error = { null }; @@ -503,9 +501,8 @@ public void onCompleted() { /** * Subscribes to the source and calls back the Observer methods on the current thread. * @param observer the observer to call event methods on - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public void subscribe(Observer observer) { final BlockingQueue queue = new LinkedBlockingQueue(); @@ -548,10 +545,9 @@ public void onCompleted() { *

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

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

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

* The class features a lockdown state, see {@link #lockdown()} and {@link #isLockdown()}, to * prevent further changes to the hooks. + * @since 1.3 */ -@Experimental public final class RxJavaHooks { /** * Prevents changing the hook callbacks when set to true. diff --git a/src/main/java/rx/plugins/RxJavaPlugins.java b/src/main/java/rx/plugins/RxJavaPlugins.java index 91ba9fa1fa..7a5856d431 100644 --- a/src/main/java/rx/plugins/RxJavaPlugins.java +++ b/src/main/java/rx/plugins/RxJavaPlugins.java @@ -17,7 +17,6 @@ import java.util.*; import java.util.concurrent.atomic.AtomicReference; -import rx.annotations.Experimental; /** * Registry for plugin implementations that allows global override and handles the retrieval of correct @@ -84,9 +83,9 @@ public static RxJavaPlugins getInstance() { * during application runtime and also bad code could invoke it in * the middle of an application life-cycle and really break applications * if not used cautiously. For more detailed discussions: - * * @see Make RxJavaPlugins.reset() public + * @see Make RxJavaPlugins.reset() public + * @since 1.3 */ - @Experimental public void reset() { INSTANCE.errorHandler.set(null); INSTANCE.observableExecutionHook.set(null); @@ -228,9 +227,8 @@ public void registerSingleExecutionHook(RxJavaSingleExecutionHook impl) { * full class name to load. * * @return {@link RxJavaCompletableExecutionHook} implementation to use - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public RxJavaCompletableExecutionHook getCompletableExecutionHook() { if (completableExecutionHook.get() == null) { // check for an implementation from System.getProperty first @@ -256,9 +254,8 @@ public RxJavaCompletableExecutionHook getCompletableExecutionHook() { * @throws IllegalStateException * if called more than once or after the default was initialized (if usage occurs before trying * to register) - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public void registerCompletableExecutionHook(RxJavaCompletableExecutionHook impl) { if (!completableExecutionHook.compareAndSet(null, impl)) { throw new IllegalStateException("Another strategy was already registered: " + singleExecutionHook.get()); diff --git a/src/main/java/rx/plugins/RxJavaSchedulersHook.java b/src/main/java/rx/plugins/RxJavaSchedulersHook.java index cc1675f322..e65ab1f3bc 100644 --- a/src/main/java/rx/plugins/RxJavaSchedulersHook.java +++ b/src/main/java/rx/plugins/RxJavaSchedulersHook.java @@ -18,7 +18,6 @@ import java.util.concurrent.ThreadFactory; import rx.Scheduler; -import rx.annotations.Experimental; import rx.functions.Action0; import rx.internal.schedulers.CachedThreadScheduler; import rx.internal.schedulers.EventLoopsScheduler; @@ -47,8 +46,8 @@ public class RxJavaSchedulersHook { /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#computation()}. * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createComputationScheduler() { return createComputationScheduler(new RxThreadFactory("RxComputationScheduler-")); } @@ -58,8 +57,8 @@ public static Scheduler createComputationScheduler() { * except using {@code threadFactory} for thread creation. * @param threadFactory the factory to use for each worker thread * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createComputationScheduler(ThreadFactory threadFactory) { if (threadFactory == null) { throw new NullPointerException("threadFactory == null"); @@ -70,8 +69,8 @@ public static Scheduler createComputationScheduler(ThreadFactory threadFactory) /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#io()}. * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createIoScheduler() { return createIoScheduler(new RxThreadFactory("RxIoScheduler-")); } @@ -81,8 +80,8 @@ public static Scheduler createIoScheduler() { * except using {@code threadFactory} for thread creation. * @param threadFactory the factory to use for each worker thread * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createIoScheduler(ThreadFactory threadFactory) { if (threadFactory == null) { throw new NullPointerException("threadFactory == null"); @@ -93,8 +92,8 @@ public static Scheduler createIoScheduler(ThreadFactory threadFactory) { /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#newThread()}. * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createNewThreadScheduler() { return createNewThreadScheduler(new RxThreadFactory("RxNewThreadScheduler-")); } @@ -104,8 +103,8 @@ public static Scheduler createNewThreadScheduler() { * except using {@code threadFactory} for thread creation. * @param threadFactory the factory to use for each worker thread * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createNewThreadScheduler(ThreadFactory threadFactory) { if (threadFactory == null) { throw new NullPointerException("threadFactory == null"); diff --git a/src/main/java/rx/schedulers/Schedulers.java b/src/main/java/rx/schedulers/Schedulers.java index e6435965e5..1327c6ba93 100644 --- a/src/main/java/rx/schedulers/Schedulers.java +++ b/src/main/java/rx/schedulers/Schedulers.java @@ -19,7 +19,6 @@ import java.util.concurrent.atomic.AtomicReference; import rx.Scheduler; -import rx.annotations.Experimental; import rx.internal.schedulers.*; import rx.plugins.*; @@ -185,8 +184,8 @@ public static Scheduler from(Executor executor) { * Resets the current {@link Schedulers} instance. * This will re-init the cached schedulers on the next usage, * which can be useful in testing. + * @since 1.3 */ - @Experimental public static void reset() { Schedulers s = INSTANCE.getAndSet(null); if (s != null) { diff --git a/src/main/java/rx/singles/BlockingSingle.java b/src/main/java/rx/singles/BlockingSingle.java index 470c012cdb..a113f272ba 100644 --- a/src/main/java/rx/singles/BlockingSingle.java +++ b/src/main/java/rx/singles/BlockingSingle.java @@ -20,7 +20,6 @@ import java.util.concurrent.atomic.AtomicReference; import rx.*; -import rx.annotations.*; import rx.exceptions.Exceptions; import rx.internal.operators.BlockingOperatorToFuture; import rx.internal.util.BlockingUtils; @@ -33,9 +32,8 @@ * or {@link Single#toBlocking()}. * * @param the value type of the sequence - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ -@Beta public final class BlockingSingle { private final Single single; diff --git a/src/main/java/rx/subjects/UnicastSubject.java b/src/main/java/rx/subjects/UnicastSubject.java index 1c51fe48bb..e7addc5cd8 100644 --- a/src/main/java/rx/subjects/UnicastSubject.java +++ b/src/main/java/rx/subjects/UnicastSubject.java @@ -19,7 +19,6 @@ import java.util.concurrent.atomic.*; import rx.*; -import rx.annotations.Experimental; import rx.exceptions.*; import rx.functions.Action0; import rx.internal.operators.*; @@ -35,8 +34,8 @@ *

* * @param the input and output value type + * @since 1.3 */ -@Experimental public final class UnicastSubject extends Subject { final State state; From daf1022ce86291791817b1ef0f45445445e9a15e Mon Sep 17 00:00:00 2001 From: David Karnok Date: Fri, 5 May 2017 09:49:16 +0200 Subject: [PATCH 10/37] Release 1.3.0 --- CHANGES.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index b02e08b281..6517018463 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,56 @@ # RxJava Releases # +### Version 1.3.0 - May 5, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.0%7C)) + +#### Summary + +Version 1.3.0 is the next minor release of the 1.x series of RxJava containing many formerly experimental API components promoted to standard. Most notably the `Completable` base reactive type is now standard as well. + +Note that the experimental `rx.Observable.fromEmitter()` has been removed in favor for the now also standard `Observable.create(Action1> emitter, Emitter.BackpressureMode backpressure)` + +The planned lifecycle of the 1.x line is as follows: + +Date | Remark +------------|------------------- + **June 1, 2017** | Feature freeze, only bugfixes from this on. + **September 1, 2017** | Release `1.4.0` finalizing the remaining API components. + **March 31, 2018** | End of development. + +The following components have been promoted to standard: + +**Classes, interfaces** + +- **classes**: `AssemblyStackTraceException`, `RxJavaCompletableExecutionHook`, `RxJavaHooks`, `UnicastSubject`, `BlockingSingle`, `Completable`, `AssertableSubscriber`, `AsyncCompletableSubscriber`, `SafeCompletableSubscriber` +- **interfaces**: `Cancellable`, `Emitter`, `SingleEmitter`, `CompletableEmitter`, `CompletableSubscriber`, `BackpressureOverflow.Strategy` + +**Operators** + +- **`Observable`**: `create`, `unsafeCreate`, `to`, `zip(Observable[], FuncN)`, `flatMapCompletable`, `flatMapSingle`, `groupby(Func1, Func1, Func1, Map>)`, `onTerminateDetach`, `rebatchRequests`, `subscribeOn(Scheduler, boolean)`, `sorted`, `withLatestFrom`, `test`, `toCompletable`, `concatDelayError`, `mergeDelayError`, `switchOnNextDelayError`, `using(Func0, Func1, Action1, boolean)`, `concatMapDelayError`, `delaySubscription(Observable)`, `distinctUntilChanged(Func2)`, `concatEager`, `concatMapEager`, `onBackpressureBuffer(long, Action0, BackpressureOverflow.Strategy)`, `switchMapDelayError`, `toSortedList(int)`, `toSortedList(Func2, int)` +- **`Completable`**: `fromEmitter`, `test` +- **`Single`**: `fromEmitter`, `merge`, `mergeDelayError`, `cache`, `to`, `doOnEach`, `doOnSuccess`, `test`, `onErrorResumeNext`, `toCompletable`, `doOnError`, `doOnSubscribe`, `delay`, `defer`, `doOnUnsubscribe`, `doAfterTerminate`, `flatMapCompletable`, `lift`, `toBlocking`, `using`, `delaySubscription(Observable)` +- **`TestSubscriber`**: `getCompletions`, `awaitValueCount`, `assertValuesAndClear` +- **`SyncOnSubscriber`**: `createSingleState`, `createStateful`, `createStateless` + +**Other** + +- `Schedulers.reset` +- `CompositeException(Throwable...)` constructor +- `Exceptions.throwOrReport` (4 overloads) +- `BlockingObservable.subscribe` (6 overloads) +- **`RxJavaSchedulersHook`**: `createComputationScheduler`, `createIoScheduler`, `createNewThreadScheduler` +- **internal**: `AssertableSubscriberObservable`, `FlatMapCompletable`, `FlatMapSingle`, `SchedulerWhen`, `BackpressureDrainManager`, `BlockingUtils`. +- **`RxJavaPlugins`**: `reset`, `getCompletableExecutionHook`, `registerCompletableExecutionHook` +- **`RxJavaErrorHandler`**: `handleOnNextValueRendering`, `render` + +In addition, the class `AsyncOnsubscribe` with its 7 factory methods and `Observable.create(AsyncOnSubscribe)` have been promoted to **beta**. + +#### Acknowledgements + +Thanks to all who contributed to the 1.x line in the past 6 months (in order they appear on the [commit](https://github.com/ReactiveX/RxJava/commits/1.x) page): + +@mtiidla, @dhendry, @mostroverkhov, @yshrsmz, @BraisGabin, @cesar1000, @Jawnnypoo, @chanx2, @abersnaze, @davidmoten, @ortex, @marwinxxii, @ekchang, @pyricau, @JakeWharton, @VictorAlbertos + + ### Version 1.2.10 - April 26, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.10%7C)) #### Bugfixes From a5c0cd0359fa2a697435f9c41b76bc0f6d6b1888 Mon Sep 17 00:00:00 2001 From: Michael Burns Date: Mon, 8 May 2017 17:10:26 -0400 Subject: [PATCH 11/37] Add the cast operator to Single. (#5332) * Add the cast operator to Single. Signed-off-by: Mike Burns * Create a separate operator for Single.cast and test it. Signed-off-by: Inferno23 * Update spacing and javadoc. Signed-off-by: Inferno23 * Add missing @Experimental annotation. Signed-off-by: Inferno23 * Fix the copyrights. Signed-off-by: Inferno23 * Remove extra verify call. Signed-off-by: Inferno23 --- src/main/java/rx/Single.java | 17 +++++++ .../operators/SingleOperatorCast.java | 37 ++++++++++++++ .../operators/SingleOperatorCastTest.java | 51 +++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 src/main/java/rx/internal/operators/SingleOperatorCast.java create mode 100644 src/test/java/rx/internal/operators/SingleOperatorCastTest.java diff --git a/src/main/java/rx/Single.java b/src/main/java/rx/Single.java index a9bff5e388..0cf0d5f06f 100644 --- a/src/main/java/rx/Single.java +++ b/src/main/java/rx/Single.java @@ -210,6 +210,23 @@ private static Observable asObservable(Single t) { * ********************************************************************************************************* */ + /** + * Casts the success value of the current Single into the target type or signals a + * ClassCastException if not compatible. + *

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

diff --git a/src/main/java/rx/internal/operators/SingleOperatorCast.java b/src/main/java/rx/internal/operators/SingleOperatorCast.java new file mode 100644 index 0000000000..b77381b161 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleOperatorCast.java @@ -0,0 +1,37 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.internal.operators; + +import rx.functions.Func1; + +/** + * Converts the element of a Single to the specified type. + * @param the input value type + * @param the output value type + */ +public class SingleOperatorCast implements Func1 { + + final Class castClass; + + public SingleOperatorCast(Class castClass) { + this.castClass = castClass; + } + + @Override + public R call(T t) { + return castClass.cast(t); + } +} diff --git a/src/test/java/rx/internal/operators/SingleOperatorCastTest.java b/src/test/java/rx/internal/operators/SingleOperatorCastTest.java new file mode 100644 index 0000000000..48d19045f5 --- /dev/null +++ b/src/test/java/rx/internal/operators/SingleOperatorCastTest.java @@ -0,0 +1,51 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.internal.operators; +; +import org.junit.Test; +import rx.Observer; +import rx.Single; + +import static org.mockito.Mockito.*; + +public class SingleOperatorCastTest { + + @Test + public void testSingleCast() { + Single source = Single.just(1); + Single single = source.cast(Integer.class); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + single.subscribe(observer); + verify(observer, times(1)).onNext(1); + verify(observer, never()).onError( + org.mockito.Matchers.any(Throwable.class)); + verify(observer, times(1)).onCompleted(); + } + + @Test + public void testSingleCastWithWrongType() { + Single source = Single.just(1); + Single single = source.cast(Boolean.class); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + single.subscribe(observer); + verify(observer, times(1)).onError( + org.mockito.Matchers.any(ClassCastException.class)); + } +} From 433b099c2d97cc24a2e7d2041755bbe5a8ddd5a0 Mon Sep 17 00:00:00 2001 From: Dave Moten Date: Thu, 18 May 2017 17:53:34 +1000 Subject: [PATCH 12/37] remove semicolon that Eclipse thinks is compile error (#5353) --- src/test/java/rx/internal/operators/SingleOperatorCastTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/rx/internal/operators/SingleOperatorCastTest.java b/src/test/java/rx/internal/operators/SingleOperatorCastTest.java index 48d19045f5..6d60c8b411 100644 --- a/src/test/java/rx/internal/operators/SingleOperatorCastTest.java +++ b/src/test/java/rx/internal/operators/SingleOperatorCastTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ package rx.internal.operators; -; + import org.junit.Test; import rx.Observer; import rx.Single; From 1dd023f073548f977ea8d199fa2151279078a340 Mon Sep 17 00:00:00 2001 From: yurgis Date: Fri, 23 Jun 2017 02:16:13 -0700 Subject: [PATCH 13/37] fix for https://github.com/ReactiveX/RxJava/issues/5429 (#5430) --- .../java/rx/observables/AsyncOnSubscribe.java | 7 ++-- .../rx/observables/AsyncOnSubscribeTest.java | 34 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/main/java/rx/observables/AsyncOnSubscribe.java b/src/main/java/rx/observables/AsyncOnSubscribe.java index e8d1ff71e6..108c9a4c8e 100644 --- a/src/main/java/rx/observables/AsyncOnSubscribe.java +++ b/src/main/java/rx/observables/AsyncOnSubscribe.java @@ -532,8 +532,11 @@ boolean tryEmit(long n) { onNextCalled = false; expectedDelivery = n; nextIteration(n); - - if (hasTerminated || isUnsubscribed()) { + + //hasTerminated will be true when onCompleted was already emitted from the request callback + //even if the the observer has not seen onCompleted from the requested observable, + //so we should not clean up while there are active subscriptions + if (hasTerminated && !subscriptions.hasSubscriptions() || isUnsubscribed()) { cleanup(); return true; } diff --git a/src/test/java/rx/observables/AsyncOnSubscribeTest.java b/src/test/java/rx/observables/AsyncOnSubscribeTest.java index d344532aae..72afaad285 100644 --- a/src/test/java/rx/observables/AsyncOnSubscribeTest.java +++ b/src/test/java/rx/observables/AsyncOnSubscribeTest.java @@ -483,4 +483,38 @@ public Integer call(Integer state, Long requested, Observer os = Observable.create(AsyncOnSubscribe. createStateful( + new Func0() { + + @Override + public Integer call() { + return 0; + } + + }, + new Func3>, Integer>() { + + @Override + public Integer call(Integer state, Long requested, Observer> emitter) { + if (state == 0) { + emitter.onNext(Observable.range(0,100).delay(1, TimeUnit.SECONDS, scheduler)); + } else { + emitter.onCompleted(); + } + return state + 1; + } + + })); + + TestSubscriber ts = new TestSubscriber(); + os.mergeWith(Observable.just(0)).subscribe(ts); + scheduler.advanceTimeBy(1, TimeUnit.HOURS); + ts.assertCompleted(); + ts.assertValueCount(101); + } + } \ No newline at end of file From 3e5b117fbfd8ef5fecc7869b84e7f6c794b1d399 Mon Sep 17 00:00:00 2001 From: Matt Laux Date: Sat, 24 Jun 2017 07:34:48 -0400 Subject: [PATCH 14/37] 1.x: TestSubscriber::assertValuesAndClear should reset valueCount (#5437) --- src/main/java/rx/observers/TestSubscriber.java | 1 + .../java/rx/observers/AssertableSubscriberTest.java | 2 +- src/test/java/rx/observers/TestSubscriberTest.java | 11 +++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/rx/observers/TestSubscriber.java b/src/main/java/rx/observers/TestSubscriber.java index 6553dcb2bf..d35602f572 100644 --- a/src/main/java/rx/observers/TestSubscriber.java +++ b/src/main/java/rx/observers/TestSubscriber.java @@ -703,5 +703,6 @@ public final void assertValuesAndClear(T expectedFirstValue, T... expectedRestVa assertItem(expectedRestValues[i], i + 1); } values.clear(); + valueCount = 0; } } diff --git a/src/test/java/rx/observers/AssertableSubscriberTest.java b/src/test/java/rx/observers/AssertableSubscriberTest.java index 838f57d6ec..4c6adbb06d 100644 --- a/src/test/java/rx/observers/AssertableSubscriberTest.java +++ b/src/test/java/rx/observers/AssertableSubscriberTest.java @@ -151,7 +151,7 @@ public void testSingle() { assertEquals(Thread.currentThread().getName(), ts.getLastSeenThread().getName()); assertTrue(ts.getOnErrorEvents().isEmpty()); assertTrue(ts.getOnNextEvents().isEmpty()); - assertEquals(1, ts.getValueCount()); + assertEquals(0, ts.getValueCount()); } @Test diff --git a/src/test/java/rx/observers/TestSubscriberTest.java b/src/test/java/rx/observers/TestSubscriberTest.java index 5e2c8edc07..a78890aa74 100644 --- a/src/test/java/rx/observers/TestSubscriberTest.java +++ b/src/test/java/rx/observers/TestSubscriberTest.java @@ -815,4 +815,15 @@ public void assertAndConsume() { ts.assertNoValues(); } + + @Test + public void assertAndClearResetsValueCount() { + TestSubscriber ts = TestSubscriber.create(); + + ts.onNext(1); + ts.assertValuesAndClear(1); + + ts.assertNoValues(); + Assert.assertEquals(0, ts.getValueCount()); + } } From b8f3cedefb350091d3daf53aa3dfabe2da5ae17d Mon Sep 17 00:00:00 2001 From: Gene Taylor Date: Thu, 6 Jul 2017 19:54:00 +1000 Subject: [PATCH 15/37] fix #5468: eager hooks onError call (#5470) * fix #5468: eager hooks onError call Move the call to RxJavaHooks into the catch block, so we only report it if the underlying subscriber throws in its onError method. This brings the behaviour in line with that of SafeSubscriber, which only call Hooks.onError inside the catch * #5468 move hooks OnError into correct if block --- src/main/java/rx/observers/SafeCompletableSubscriber.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/rx/observers/SafeCompletableSubscriber.java b/src/main/java/rx/observers/SafeCompletableSubscriber.java index 6f96ecfc0d..7430fa371a 100644 --- a/src/main/java/rx/observers/SafeCompletableSubscriber.java +++ b/src/main/java/rx/observers/SafeCompletableSubscriber.java @@ -54,8 +54,8 @@ public void onCompleted() { @Override public void onError(Throwable e) { - RxJavaHooks.onError(e); if (done) { + RxJavaHooks.onError(e); return; } done = true; From 8e925345814c291e2ab2f10f4986f1ac9b8ee68a Mon Sep 17 00:00:00 2001 From: David Karnok Date: Thu, 6 Jul 2017 22:23:10 +0200 Subject: [PATCH 16/37] 1.x: increase timeout of some tests (#5471) * 1.x: increase timeout of some tests * OperatorFlatMapTest.flatMapRangeMixedAsyncLoop more time * Retry testNoBufferingOrBlockingOfSequence multiple times --- src/main/java/rx/SingleEmitter.java | 2 +- .../java/rx/observables/AsyncOnSubscribe.java | 6 +- .../operators/BlockingOperatorNextTest.java | 105 ++++++++++-------- .../operators/CompletableOnErrorXTest.java | 4 +- .../operators/OperatorFlatMapTest.java | 2 +- .../OperatorMergeMaxConcurrentTest.java | 6 +- .../operators/OperatorSwitchTest.java | 2 +- 7 files changed, 69 insertions(+), 58 deletions(-) diff --git a/src/main/java/rx/SingleEmitter.java b/src/main/java/rx/SingleEmitter.java index a60d0840a5..53d034a076 100644 --- a/src/main/java/rx/SingleEmitter.java +++ b/src/main/java/rx/SingleEmitter.java @@ -23,7 +23,7 @@ *

* All methods are thread-safe; calling onSuccess or onError twice or one after the other has * no effect. - *

History: 1.2.3 - experimental + *

History: 1.2.3 - experimental * @param the success value type * @since 1.3 */ diff --git a/src/main/java/rx/observables/AsyncOnSubscribe.java b/src/main/java/rx/observables/AsyncOnSubscribe.java index 108c9a4c8e..7a1eef7112 100644 --- a/src/main/java/rx/observables/AsyncOnSubscribe.java +++ b/src/main/java/rx/observables/AsyncOnSubscribe.java @@ -532,9 +532,9 @@ boolean tryEmit(long n) { onNextCalled = false; expectedDelivery = n; nextIteration(n); - - //hasTerminated will be true when onCompleted was already emitted from the request callback - //even if the the observer has not seen onCompleted from the requested observable, + + //hasTerminated will be true when onCompleted was already emitted from the request callback + //even if the the observer has not seen onCompleted from the requested observable, //so we should not clean up while there are active subscriptions if (hasTerminated && !subscriptions.hasSubscriptions() || isUnsubscribed()) { cleanup(); diff --git a/src/test/java/rx/internal/operators/BlockingOperatorNextTest.java b/src/test/java/rx/internal/operators/BlockingOperatorNextTest.java index 4cff913cea..69541f712b 100644 --- a/src/test/java/rx/internal/operators/BlockingOperatorNextTest.java +++ b/src/test/java/rx/internal/operators/BlockingOperatorNextTest.java @@ -234,69 +234,80 @@ public void testNextWithCallingHasNextMultipleTimes() { */ @Test public void testNoBufferingOrBlockingOfSequence() throws Throwable { - final CountDownLatch finished = new CountDownLatch(1); - final int COUNT = 30; - final CountDownLatch timeHasPassed = new CountDownLatch(COUNT); - final AtomicBoolean running = new AtomicBoolean(true); - final AtomicInteger count = new AtomicInteger(0); - final Observable obs = Observable.unsafeCreate(new Observable.OnSubscribe() { + int retries = 10; - @Override - public void call(final Subscriber o) { - new Thread(new Runnable() { + for (;;) { + try { + final CountDownLatch finished = new CountDownLatch(1); + final int COUNT = 30; + final CountDownLatch timeHasPassed = new CountDownLatch(COUNT); + final AtomicBoolean running = new AtomicBoolean(true); + final AtomicInteger count = new AtomicInteger(0); + final Observable obs = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override - public void run() { - try { - while (running.get()) { - o.onNext(count.incrementAndGet()); - timeHasPassed.countDown(); + public void call(final Subscriber o) { + new Thread(new Runnable() { + + @Override + public void run() { + try { + while (running.get()) { + o.onNext(count.incrementAndGet()); + timeHasPassed.countDown(); + } + o.onCompleted(); + } catch (Throwable e) { + o.onError(e); + } finally { + finished.countDown(); + } } - o.onCompleted(); - } catch (Throwable e) { - o.onError(e); - } finally { - finished.countDown(); - } + }).start(); } - }).start(); - } - }); + }); - try { - Iterator it = next(obs).iterator(); + try { + Iterator it = next(obs).iterator(); - assertTrue(it.hasNext()); - int a = it.next(); - assertTrue(it.hasNext()); - int b = it.next(); - // we should have a different value - assertTrue("a and b should be different", a != b); + assertTrue(it.hasNext()); + int a = it.next(); + assertTrue(it.hasNext()); + int b = it.next(); + // we should have a different value + assertTrue("a and b should be different", a != b); - // wait for some time (if times out we are blocked somewhere so fail ... set very high for very slow, constrained machines) - timeHasPassed.await(8000, TimeUnit.MILLISECONDS); + // wait for some time (if times out we are blocked somewhere so fail ... set very high for very slow, constrained machines) + timeHasPassed.await(8000, TimeUnit.MILLISECONDS); - assertTrue(it.hasNext()); - int c = it.next(); + assertTrue(it.hasNext()); + int c = it.next(); - assertTrue("c should not just be the next in sequence", c != (b + 1)); - assertTrue("expected that c [" + c + "] is higher than or equal to " + COUNT, c >= COUNT); + assertTrue("c should not just be the next in sequence", c != (b + 1)); + assertTrue("expected that c [" + c + "] is higher than or equal to " + COUNT, c >= COUNT); - assertTrue(it.hasNext()); - int d = it.next(); - assertTrue(d > c); + assertTrue(it.hasNext()); + int d = it.next(); + assertTrue(d > c); - // shut down the thread - running.set(false); + // shut down the thread + running.set(false); - finished.await(); + finished.await(); - assertFalse(it.hasNext()); + assertFalse(it.hasNext()); - System.out.println("a: " + a + " b: " + b + " c: " + c); - } finally { - running.set(false); // don't let the thread spin indefinitely + System.out.println("a: " + a + " b: " + b + " c: " + c); + } finally { + running.set(false); // don't let the thread spin indefinitely + } + return; + } catch (AssertionError ex) { + if (retries-- == 0) { + throw ex; + } + } } } diff --git a/src/test/java/rx/internal/operators/CompletableOnErrorXTest.java b/src/test/java/rx/internal/operators/CompletableOnErrorXTest.java index c462352af3..31cbff9df1 100644 --- a/src/test/java/rx/internal/operators/CompletableOnErrorXTest.java +++ b/src/test/java/rx/internal/operators/CompletableOnErrorXTest.java @@ -29,7 +29,7 @@ public class CompletableOnErrorXTest { @Test public void nextUnsubscribe() { PublishSubject ps = PublishSubject.create(); - + AssertableSubscriber as = ps.toCompletable() .onErrorResumeNext(new Func1() { @Override @@ -49,7 +49,7 @@ public Completable call(Throwable e) { @Test public void completeUnsubscribe() { PublishSubject ps = PublishSubject.create(); - + AssertableSubscriber as = ps.toCompletable() .onErrorComplete() .test(); diff --git a/src/test/java/rx/internal/operators/OperatorFlatMapTest.java b/src/test/java/rx/internal/operators/OperatorFlatMapTest.java index 61805d7372..286a2a03bc 100644 --- a/src/test/java/rx/internal/operators/OperatorFlatMapTest.java +++ b/src/test/java/rx/internal/operators/OperatorFlatMapTest.java @@ -468,7 +468,7 @@ public Observable call(Integer t) { } } } - @Test(timeout = 30000) + @Test(timeout = 60000) public void flatMapRangeMixedAsyncLoop() { for (int i = 0; i < 2000; i++) { if (i % 10 == 0) { diff --git a/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java b/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java index 21ce364b34..5be03a3b6f 100644 --- a/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java +++ b/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java @@ -197,7 +197,7 @@ public void testSimpleAsyncLoop() { testSimpleAsync(); } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testSimpleAsync() { for (int i = 1; i < 50; i++) { TestSubscriber ts = new TestSubscriber(); @@ -217,7 +217,7 @@ public void testSimpleAsync() { assertEquals(expected, actual); } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testSimpleOneLessAsyncLoop() { int max = 200; if (PlatformDependent.isAndroid()) { @@ -227,7 +227,7 @@ public void testSimpleOneLessAsyncLoop() { testSimpleOneLessAsync(); } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testSimpleOneLessAsync() { long t = System.currentTimeMillis(); for (int i = 2; i < 50; i++) { diff --git a/src/test/java/rx/internal/operators/OperatorSwitchTest.java b/src/test/java/rx/internal/operators/OperatorSwitchTest.java index 8525c18cd6..00887db5e1 100644 --- a/src/test/java/rx/internal/operators/OperatorSwitchTest.java +++ b/src/test/java/rx/internal/operators/OperatorSwitchTest.java @@ -894,7 +894,7 @@ public void asyncInner() throws Throwable { .switchMap(UtilityFunctions.>identity()) .observeOn(Schedulers.computation()) .ignoreElements() - .timeout(5, TimeUnit.SECONDS) + .timeout(15, TimeUnit.SECONDS) .toBlocking() .subscribe(Actions.empty(), new Action1() { @Override From 85350bf1465416fbc23a54f465e9b75cb71fd361 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Sun, 10 Sep 2017 18:19:29 +0200 Subject: [PATCH 17/37] Update CHANGES.md --- CHANGES.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 6517018463..373c3665c9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,18 @@ # RxJava Releases # +### Version 1.3.1 - September 10, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.1%7C)) + +#### API changes +*Remark: submitted & merged before the feature freeze of June 1st.* + +- [Pull 5332](https://github.com/ReactiveX/RxJava/pull/5332): Add the `cast` operator to `Single`. + +#### Bugfixes + +- [Pull 5430](https://github.com/ReactiveX/RxJava/pull/5430): Fix premature cleanup in `AsyncOnSubscribe` when the last `Observable` is still running. +- [Pull 5437](https://github.com/ReactiveX/RxJava/pull/5437): `TestSubscriber::assertValuesAndClear` should reset `valueCount`. +- [Pull 5470](https://github.com/ReactiveX/RxJava/pull/5470): Fix eager call to `RxJavHooks.onError` in `SafeCompletableSuscriber`. + ### Version 1.3.0 - May 5, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.0%7C)) #### Summary From ed35a14b8b8c5899f292e60905e082563d516539 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Sun, 10 Sep 2017 18:21:42 +0200 Subject: [PATCH 18/37] Use oraclejdk8 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f07cf8d5e4..751937fa52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: java jdk: -- oraclejdk7 +- oraclejdk8 sudo: false # as per http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ From 50ea46a344485df787d938a954cf871f36abc44a Mon Sep 17 00:00:00 2001 From: David Karnok Date: Fri, 15 Sep 2017 09:27:06 +0200 Subject: [PATCH 19/37] 1.x: Workaround for CHM.keySet bad type with Java 8 compiler (#5602) --- src/main/java/rx/internal/schedulers/NewThreadWorker.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/rx/internal/schedulers/NewThreadWorker.java b/src/main/java/rx/internal/schedulers/NewThreadWorker.java index dd63eddf9a..23f8af90e2 100644 --- a/src/main/java/rx/internal/schedulers/NewThreadWorker.java +++ b/src/main/java/rx/internal/schedulers/NewThreadWorker.java @@ -16,7 +16,7 @@ package rx.internal.schedulers; import java.lang.reflect.*; -import java.util.Iterator; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; @@ -110,10 +110,12 @@ public static void deregisterExecutor(ScheduledExecutorService service) { } /** Purges each registered executor and eagerly evicts shutdown executors. */ - @SuppressAnimalSniffer // CHM.keySet returns KeySetView in Java 8+; false positive here static void purgeExecutors() { try { - Iterator it = EXECUTORS.keySet().iterator(); + // This prevents map.keySet to compile to a Java 8+ KeySetView return type + // and cause NoSuchMethodError on Java 6-7 runtimes. + Map map = EXECUTORS; + Iterator it = map.keySet().iterator(); while (it.hasNext()) { ScheduledThreadPoolExecutor exec = it.next(); if (!exec.isShutdown()) { From 81542cdc4278c8d4509a66441976d908a2197ea5 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Fri, 15 Sep 2017 09:35:00 +0200 Subject: [PATCH 20/37] Release 1.3.2 --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 373c3665c9..3a834830b7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # RxJava Releases # +### Version 1.3.2 - September 15, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.2%7C)) + +#### Bugfixes + +- [Pull 5602](https://github.com/ReactiveX/RxJava/pull/5602): Workaround for CHM.keySet bad type with Java 8 compiler + ### Version 1.3.1 - September 10, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.1%7C)) #### API changes From 0660284a9ba53e755c04c407c1dcdd5d4c7fd85b Mon Sep 17 00:00:00 2001 From: David Karnok Date: Fri, 13 Oct 2017 21:11:12 +0200 Subject: [PATCH 21/37] 1.x: fix timeout (timed, selector) unsubscribe bug (#5660) * 1.x: fix timeout(time, [fallback]) unsubscribe bug * Fix timeout(selector) variant. --- src/main/java/rx/Observable.java | 6 +- ...nSubscribeTimeoutSelectorWithFallback.java | 247 ++++++++++++++++++ .../OnSubscribeTimeoutTimedWithFallback.java | 227 ++++++++++++++++ .../internal/operators/OperatorTimeout.java | 58 ---- .../operators/OperatorTimeoutBase.java | 206 --------------- .../OperatorTimeoutWithSelector.java | 111 -------- .../operators/OperatorTimeoutTests.java | 78 +++++- .../OperatorTimeoutWithSelectorTest.java | 178 ++++++++++++- 8 files changed, 726 insertions(+), 385 deletions(-) create mode 100644 src/main/java/rx/internal/operators/OnSubscribeTimeoutSelectorWithFallback.java create mode 100644 src/main/java/rx/internal/operators/OnSubscribeTimeoutTimedWithFallback.java delete mode 100644 src/main/java/rx/internal/operators/OperatorTimeout.java delete mode 100644 src/main/java/rx/internal/operators/OperatorTimeoutBase.java delete mode 100644 src/main/java/rx/internal/operators/OperatorTimeoutWithSelector.java diff --git a/src/main/java/rx/Observable.java b/src/main/java/rx/Observable.java index 6c4b599bb6..4b3d7802b4 100644 --- a/src/main/java/rx/Observable.java +++ b/src/main/java/rx/Observable.java @@ -11284,11 +11284,13 @@ public final Observable timeout(Func0> firstTi * if {@code timeoutSelector} is null * @see ReactiveX operators documentation: Timeout */ + @SuppressWarnings("unchecked") public final Observable timeout(Func0> firstTimeoutSelector, Func1> timeoutSelector, Observable other) { if (timeoutSelector == null) { throw new NullPointerException("timeoutSelector is null"); } - return lift(new OperatorTimeoutWithSelector(firstTimeoutSelector, timeoutSelector, other)); + return unsafeCreate(new OnSubscribeTimeoutSelectorWithFallback(this, + firstTimeoutSelector != null ? defer((Func0>)firstTimeoutSelector) : null, timeoutSelector, other)); } /** @@ -11443,7 +11445,7 @@ public final Observable timeout(long timeout, TimeUnit timeUnit, ObservableReactiveX operators documentation: Timeout */ public final Observable timeout(long timeout, TimeUnit timeUnit, Observable other, Scheduler scheduler) { - return lift(new OperatorTimeout(timeout, timeUnit, other, scheduler)); + return unsafeCreate(new OnSubscribeTimeoutTimedWithFallback(this, timeout, timeUnit, scheduler, other)); } /** diff --git a/src/main/java/rx/internal/operators/OnSubscribeTimeoutSelectorWithFallback.java b/src/main/java/rx/internal/operators/OnSubscribeTimeoutSelectorWithFallback.java new file mode 100644 index 0000000000..75d75777c8 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeTimeoutSelectorWithFallback.java @@ -0,0 +1,247 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.operators; + +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicLong; + +import rx.*; +import rx.exceptions.Exceptions; +import rx.functions.Func1; +import rx.internal.operators.OnSubscribeTimeoutTimedWithFallback.FallbackSubscriber; +import rx.internal.producers.ProducerArbiter; +import rx.internal.subscriptions.SequentialSubscription; +import rx.plugins.RxJavaHooks; + +/** + * Switches to the fallback Observable if: the first upstream item doesn't arrive before + * the first timeout Observable signals an item or completes; or the Observable generated from + * the previous upstream item signals its item or completes before the upstream signals the next item + * of its own. + * + * @param the input and output value type + * @param the value type of the first timeout Observable + * @param the value type of the item-based timeout Observable + * + * @since 1.3.3 + */ +public final class OnSubscribeTimeoutSelectorWithFallback implements Observable.OnSubscribe { + + final Observable source; + + final Observable firstTimeoutIndicator; + + final Func1> itemTimeoutIndicator; + + final Observable fallback; + + public OnSubscribeTimeoutSelectorWithFallback(Observable source, + Observable firstTimeoutIndicator, + Func1> itemTimeoutIndicator, + Observable fallback) { + this.source = source; + this.firstTimeoutIndicator = firstTimeoutIndicator; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.fallback = fallback; + } + + @Override + public void call(Subscriber t) { + TimeoutMainSubscriber parent = new TimeoutMainSubscriber(t, itemTimeoutIndicator, fallback); + t.add(parent.upstream); + t.setProducer(parent.arbiter); + parent.startFirst(firstTimeoutIndicator); + source.subscribe(parent); + } + + static final class TimeoutMainSubscriber extends Subscriber { + + final Subscriber actual; + + final Func1> itemTimeoutIndicator; + + final Observable fallback; + + final ProducerArbiter arbiter; + + final AtomicLong index; + + final SequentialSubscription task; + + final SequentialSubscription upstream; + + long consumed; + + TimeoutMainSubscriber(Subscriber actual, + Func1> itemTimeoutIndicator, + Observable fallback) { + this.actual = actual; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.fallback = fallback; + this.arbiter = new ProducerArbiter(); + this.index = new AtomicLong(); + this.task = new SequentialSubscription(); + this.upstream = new SequentialSubscription(this); + this.add(task); + } + + + @Override + public void onNext(T t) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { + return; + } + + Subscription s = task.get(); + if (s != null) { + s.unsubscribe(); + } + + actual.onNext(t); + + consumed++; + + Observable timeoutObservable; + + try { + timeoutObservable = itemTimeoutIndicator.call(t); + if (timeoutObservable == null) { + throw new NullPointerException("The itemTimeoutIndicator returned a null Observable"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + index.getAndSet(Long.MAX_VALUE); + actual.onError(ex); + return; + } + + TimeoutConsumer tc = new TimeoutConsumer(idx + 1); + if (task.replace(tc)) { + timeoutObservable.subscribe(tc); + } + + } + + void startFirst(Observable firstTimeoutIndicator) { + if (firstTimeoutIndicator != null) { + TimeoutConsumer tc = new TimeoutConsumer(0L); + if (task.replace(tc)) { + firstTimeoutIndicator.subscribe(tc); + } + } + } + + @Override + public void onError(Throwable e) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.unsubscribe(); + + actual.onError(e); + } else { + RxJavaHooks.onError(e); + } + } + + @Override + public void onCompleted() { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.unsubscribe(); + + actual.onCompleted(); + } + } + + @Override + public void setProducer(Producer p) { + arbiter.setProducer(p); + } + + void onTimeout(long idx) { + if (!index.compareAndSet(idx, Long.MAX_VALUE)) { + return; + } + + unsubscribe(); + + if (fallback == null) { + actual.onError(new TimeoutException()); + } else { + long c = consumed; + if (c != 0L) { + arbiter.produced(c); + } + + FallbackSubscriber fallbackSubscriber = new FallbackSubscriber(actual, arbiter); + + if (upstream.replace(fallbackSubscriber)) { + fallback.subscribe(fallbackSubscriber); + } + } + } + + void onTimeoutError(long idx, Throwable ex) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + unsubscribe(); + + actual.onError(ex); + } else { + RxJavaHooks.onError(ex); + } + + } + + final class TimeoutConsumer extends Subscriber { + + final long idx; + + boolean done; + + TimeoutConsumer(long idx) { + this.idx = idx; + } + + @Override + public void onNext(Object t) { + if (!done) { + done = true; + unsubscribe(); + onTimeout(idx); + } + } + + @Override + public void onError(Throwable e) { + if (!done) { + done = true; + onTimeoutError(idx, e); + } else { + RxJavaHooks.onError(e); + } + } + + @Override + public void onCompleted() { + if (!done) { + done = true; + onTimeout(idx); + } + } + } + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeTimeoutTimedWithFallback.java b/src/main/java/rx/internal/operators/OnSubscribeTimeoutTimedWithFallback.java new file mode 100644 index 0000000000..e70c57d667 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeTimeoutTimedWithFallback.java @@ -0,0 +1,227 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.operators; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; + +import rx.*; +import rx.Scheduler.Worker; +import rx.functions.Action0; +import rx.internal.producers.ProducerArbiter; +import rx.internal.subscriptions.SequentialSubscription; +import rx.plugins.RxJavaHooks; + +/** + * Switches to consuming a fallback Observable if the main source doesn't signal an onNext event + * within the given time frame after subscription or the previous onNext event. + * + * @param the value type + * @since 1.3.3 + */ +public final class OnSubscribeTimeoutTimedWithFallback implements Observable.OnSubscribe { + + final Observable source; + + final long timeout; + + final TimeUnit unit; + + final Scheduler scheduler; + + final Observable fallback; + + public OnSubscribeTimeoutTimedWithFallback(Observable source, long timeout, + TimeUnit unit, Scheduler scheduler, + Observable fallback) { + this.source = source; + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.fallback = fallback; + } + + @Override + public void call(Subscriber t) { + TimeoutMainSubscriber parent = new TimeoutMainSubscriber(t, timeout, unit, scheduler.createWorker(), fallback); + t.add(parent.upstream); + t.setProducer(parent.arbiter); + parent.startTimeout(0L); + source.subscribe(parent); + } + + static final class TimeoutMainSubscriber extends Subscriber { + + final Subscriber actual; + + final long timeout; + + final TimeUnit unit; + + final Worker worker; + + final Observable fallback; + + final ProducerArbiter arbiter; + + final AtomicLong index; + + final SequentialSubscription task; + + final SequentialSubscription upstream; + + long consumed; + + TimeoutMainSubscriber(Subscriber actual, long timeout, + TimeUnit unit, Worker worker, + Observable fallback) { + this.actual = actual; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + this.fallback = fallback; + this.arbiter = new ProducerArbiter(); + this.index = new AtomicLong(); + this.task = new SequentialSubscription(); + this.upstream = new SequentialSubscription(this); + this.add(worker); + this.add(task); + } + + + @Override + public void onNext(T t) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { + return; + } + + Subscription s = task.get(); + if (s != null) { + s.unsubscribe(); + } + + consumed++; + + actual.onNext(t); + + startTimeout(idx + 1); + } + + void startTimeout(long nextIdx) { + task.replace(worker.schedule(new TimeoutTask(nextIdx), timeout, unit)); + } + + @Override + public void onError(Throwable e) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.unsubscribe(); + + actual.onError(e); + + worker.unsubscribe(); + } else { + RxJavaHooks.onError(e); + } + } + + @Override + public void onCompleted() { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.unsubscribe(); + + actual.onCompleted(); + + worker.unsubscribe(); + } + } + + @Override + public void setProducer(Producer p) { + arbiter.setProducer(p); + } + + void onTimeout(long idx) { + if (!index.compareAndSet(idx, Long.MAX_VALUE)) { + return; + } + + unsubscribe(); + + if (fallback == null) { + actual.onError(new TimeoutException()); + } else { + long c = consumed; + if (c != 0L) { + arbiter.produced(c); + } + + FallbackSubscriber fallbackSubscriber = new FallbackSubscriber(actual, arbiter); + + if (upstream.replace(fallbackSubscriber)) { + fallback.subscribe(fallbackSubscriber); + } + } + } + + final class TimeoutTask implements Action0 { + + final long idx; + + TimeoutTask(long idx) { + this.idx = idx; + } + + @Override + public void call() { + onTimeout(idx); + } + } + } + + static final class FallbackSubscriber extends Subscriber { + + final Subscriber actual; + + final ProducerArbiter arbiter; + + FallbackSubscriber(Subscriber actual, ProducerArbiter arbiter) { + this.actual = actual; + this.arbiter = arbiter; + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable e) { + actual.onError(e); + } + + @Override + public void onCompleted() { + actual.onCompleted(); + } + + @Override + public void setProducer(Producer p) { + arbiter.setProducer(p); + } + } +} diff --git a/src/main/java/rx/internal/operators/OperatorTimeout.java b/src/main/java/rx/internal/operators/OperatorTimeout.java deleted file mode 100644 index 3c74663e80..0000000000 --- a/src/main/java/rx/internal/operators/OperatorTimeout.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.internal.operators; - -import java.util.concurrent.TimeUnit; - -import rx.*; -import rx.functions.Action0; - -/** - * Applies a timeout policy for each element in the observable sequence, using - * the specified scheduler to run timeout timers. If the next element isn't - * received within the specified timeout duration starting from its predecessor, - * the other observable sequence is used to produce future messages from that - * point on. - * @param the value type - */ -public final class OperatorTimeout extends OperatorTimeoutBase { - - public OperatorTimeout(final long timeout, final TimeUnit timeUnit, Observable other, Scheduler scheduler) { - super(new FirstTimeoutStub() { - - @Override - public Subscription call(final TimeoutSubscriber timeoutSubscriber, final Long seqId, Scheduler.Worker inner) { - return inner.schedule(new Action0() { - @Override - public void call() { - timeoutSubscriber.onTimeout(seqId); - } - }, timeout, timeUnit); - } - }, new TimeoutStub() { - - @Override - public Subscription call(final TimeoutSubscriber timeoutSubscriber, final Long seqId, T value, Scheduler.Worker inner) { - return inner.schedule(new Action0() { - @Override - public void call() { - timeoutSubscriber.onTimeout(seqId); - } - }, timeout, timeUnit); - } - }, other, scheduler); - } -} diff --git a/src/main/java/rx/internal/operators/OperatorTimeoutBase.java b/src/main/java/rx/internal/operators/OperatorTimeoutBase.java deleted file mode 100644 index 435a60e503..0000000000 --- a/src/main/java/rx/internal/operators/OperatorTimeoutBase.java +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.internal.operators; - -import java.util.concurrent.TimeoutException; - -import rx.*; -import rx.Observable.Operator; -import rx.functions.*; -import rx.internal.producers.ProducerArbiter; -import rx.observers.SerializedSubscriber; -import rx.subscriptions.SerialSubscription; - -class OperatorTimeoutBase implements Operator { - final FirstTimeoutStub firstTimeoutStub; - final TimeoutStub timeoutStub; - final Observable other; - final Scheduler scheduler; - - /** - * Set up the timeout action on the first value. - * - * @param - */ - /* package-private */interface FirstTimeoutStub extends - Func3, Long, Scheduler.Worker, Subscription> { - } - - /** - * Set up the timeout action based on every value - * - * @param - */ - /* package-private */interface TimeoutStub extends - Func4, Long, T, Scheduler.Worker, Subscription> { - } - - /* package-private */OperatorTimeoutBase(FirstTimeoutStub firstTimeoutStub, TimeoutStub timeoutStub, Observable other, Scheduler scheduler) { - this.firstTimeoutStub = firstTimeoutStub; - this.timeoutStub = timeoutStub; - this.other = other; - this.scheduler = scheduler; - } - - @Override - public Subscriber call(Subscriber subscriber) { - Scheduler.Worker inner = scheduler.createWorker(); - subscriber.add(inner); - // Use SynchronizedSubscriber for safe memory access - // as the subscriber will be accessed in the current thread or the - // scheduler or other Observables. - final SerializedSubscriber synchronizedSubscriber = new SerializedSubscriber(subscriber); - - final SerialSubscription serial = new SerialSubscription(); - synchronizedSubscriber.add(serial); - - TimeoutSubscriber timeoutSubscriber = new TimeoutSubscriber(synchronizedSubscriber, timeoutStub, serial, other, inner); - - synchronizedSubscriber.add(timeoutSubscriber); - synchronizedSubscriber.setProducer(timeoutSubscriber.arbiter); - - serial.set(firstTimeoutStub.call(timeoutSubscriber, 0L, inner)); - - return timeoutSubscriber; - } - - /* package-private */static final class TimeoutSubscriber extends - Subscriber { - - final SerialSubscription serial; - - final SerializedSubscriber serializedSubscriber; - - final TimeoutStub timeoutStub; - - final Observable other; - - final Scheduler.Worker inner; - - final ProducerArbiter arbiter; - - /** Guarded by this. */ - boolean terminated; - /** Guarded by this. */ - long actual; - - TimeoutSubscriber( - SerializedSubscriber serializedSubscriber, - TimeoutStub timeoutStub, SerialSubscription serial, - Observable other, - Scheduler.Worker inner) { - this.serializedSubscriber = serializedSubscriber; - this.timeoutStub = timeoutStub; - this.serial = serial; - this.other = other; - this.inner = inner; - this.arbiter = new ProducerArbiter(); - } - - @Override - public void setProducer(Producer p) { - arbiter.setProducer(p); - } - - @Override - public void onNext(T value) { - boolean onNextWins = false; - long a; - synchronized (this) { - if (!terminated) { - a = ++actual; - onNextWins = true; - } else { - a = actual; - } - } - if (onNextWins) { - serializedSubscriber.onNext(value); - serial.set(timeoutStub.call(this, a, value, inner)); - } - } - - @Override - public void onError(Throwable error) { - boolean onErrorWins = false; - synchronized (this) { - if (!terminated) { - terminated = true; - onErrorWins = true; - } - } - if (onErrorWins) { - serial.unsubscribe(); - serializedSubscriber.onError(error); - } - } - - @Override - public void onCompleted() { - boolean onCompletedWins = false; - synchronized (this) { - if (!terminated) { - terminated = true; - onCompletedWins = true; - } - } - if (onCompletedWins) { - serial.unsubscribe(); - serializedSubscriber.onCompleted(); - } - } - - public void onTimeout(long seqId) { - long expected = seqId; - boolean timeoutWins = false; - synchronized (this) { - if (expected == actual && !terminated) { - terminated = true; - timeoutWins = true; - } - } - if (timeoutWins) { - if (other == null) { - serializedSubscriber.onError(new TimeoutException()); - } else { - Subscriber second = new Subscriber() { - @Override - public void onNext(T t) { - serializedSubscriber.onNext(t); - } - - @Override - public void onError(Throwable e) { - serializedSubscriber.onError(e); - } - - @Override - public void onCompleted() { - serializedSubscriber.onCompleted(); - } - - @Override - public void setProducer(Producer p) { - arbiter.setProducer(p); - } - }; - other.unsafeSubscribe(second); - serial.set(second); - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OperatorTimeoutWithSelector.java b/src/main/java/rx/internal/operators/OperatorTimeoutWithSelector.java deleted file mode 100644 index 16034dba6c..0000000000 --- a/src/main/java/rx/internal/operators/OperatorTimeoutWithSelector.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.internal.operators; - -import rx.*; -import rx.exceptions.Exceptions; -import rx.functions.*; -import rx.schedulers.Schedulers; -import rx.subscriptions.Subscriptions; - -/** - * Returns an Observable that mirrors the source Observable. If either the first - * item emitted by the source Observable or any subsequent item don't arrive - * within time windows defined by provided Observables, switch to the - * other Observable if provided, or emit a TimeoutException . - * @param the value type of the main Observable - * @param the value type of the first timeout Observable - * @param the value type of the subsequent timeout Observable - */ -public class OperatorTimeoutWithSelector extends - OperatorTimeoutBase { - - public OperatorTimeoutWithSelector( - final Func0> firstTimeoutSelector, - final Func1> timeoutSelector, - Observable other) { - super(new FirstTimeoutStub() { - - @Override - public Subscription call( - final TimeoutSubscriber timeoutSubscriber, - final Long seqId, Scheduler.Worker inner) { - if (firstTimeoutSelector != null) { - Observable o; - try { - o = firstTimeoutSelector.call(); - } catch (Throwable t) { - Exceptions.throwOrReport(t, timeoutSubscriber); - return Subscriptions.unsubscribed(); - } - return o.unsafeSubscribe(new Subscriber() { - - @Override - public void onCompleted() { - timeoutSubscriber.onTimeout(seqId); - } - - @Override - public void onError(Throwable e) { - timeoutSubscriber.onError(e); - } - - @Override - public void onNext(U t) { - timeoutSubscriber.onTimeout(seqId); - } - - }); - } else { - return Subscriptions.unsubscribed(); - } - } - }, new TimeoutStub() { - - @Override - public Subscription call( - final TimeoutSubscriber timeoutSubscriber, - final Long seqId, T value, Scheduler.Worker inner) { - Observable o; - try { - o = timeoutSelector.call(value); - } catch (Throwable t) { - Exceptions.throwOrReport(t, timeoutSubscriber); - return Subscriptions.unsubscribed(); - } - return o.unsafeSubscribe(new Subscriber() { - - @Override - public void onCompleted() { - timeoutSubscriber.onTimeout(seqId); - } - - @Override - public void onError(Throwable e) { - timeoutSubscriber.onError(e); - } - - @Override - public void onNext(V t) { - timeoutSubscriber.onTimeout(seqId); - } - - }); - } - }, other, Schedulers.immediate()); - } - -} diff --git a/src/test/java/rx/internal/operators/OperatorTimeoutTests.java b/src/test/java/rx/internal/operators/OperatorTimeoutTests.java index fba5b1d6c5..6be7706d0f 100644 --- a/src/test/java/rx/internal/operators/OperatorTimeoutTests.java +++ b/src/test/java/rx/internal/operators/OperatorTimeoutTests.java @@ -15,21 +15,26 @@ */ package rx.internal.operators; +import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; import java.io.IOException; +import java.util.*; import java.util.concurrent.*; import org.junit.*; import org.mockito.*; import rx.*; +import rx.Observable; import rx.Observable.OnSubscribe; -import rx.functions.Func1; -import rx.observers.TestSubscriber; +import rx.Observer; +import rx.exceptions.TestException; +import rx.functions.*; +import rx.observers.*; import rx.schedulers.TestScheduler; -import rx.subjects.PublishSubject; +import rx.subjects.*; public class OperatorTimeoutTests { private PublishSubject underlyingSubject; @@ -427,4 +432,71 @@ public void withDefaultSchedulerAndOther() { ts.assertNoErrors(); ts.assertCompleted(); } + + @Test + public void disconnectOnTimeout() { + final List list = Collections.synchronizedList(new ArrayList()); + + TestScheduler sch = new TestScheduler(); + + Subject subject = PublishSubject.create(); + Observable initialObservable = subject.share() + .map(new Func1() { + @Override + public Long call(Long value) { + list.add("Received value " + value); + return value; + } + }); + + Observable timeoutObservable = initialObservable + .map(new Func1() { + @Override + public Long call(Long value) { + list.add("Timeout received value " + value); + return value; + } + }); + + TestSubscriber subscriber = new TestSubscriber(); + initialObservable + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + list.add("Unsubscribed"); + } + }) + .timeout(1, TimeUnit.SECONDS, timeoutObservable, sch).subscribe(subscriber); + + subject.onNext(5L); + + sch.advanceTimeBy(2, TimeUnit.SECONDS); + + subject.onNext(10L); + subject.onCompleted(); + + subscriber.awaitTerminalEvent(); + subscriber.assertNoErrors(); + subscriber.assertValues(5L, 10L); + + assertEquals(Arrays.asList( + "Received value 5", + "Unsubscribed", + "Received value 10", + "Timeout received value 10" + ), list); + } + + @Test + public void fallbackIsError() { + TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.never() + .timeout(1, TimeUnit.SECONDS, Observable.error(new TestException()), sch) + .test(); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertFailure(TestException.class); + } } diff --git a/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java b/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java index 50ce9e5865..628d1eca55 100644 --- a/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java +++ b/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java @@ -19,7 +19,8 @@ import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; -import java.util.Arrays; +import java.io.IOException; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -28,13 +29,15 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import rx.*; +import rx.Observable; import rx.Observable.OnSubscribe; +import rx.Observer; +import rx.Subscriber; import rx.exceptions.TestException; import rx.functions.*; -import rx.observers.TestSubscriber; -import rx.schedulers.Schedulers; -import rx.subjects.PublishSubject; +import rx.observers.*; +import rx.schedulers.*; +import rx.subjects.*; public class OperatorTimeoutWithSelectorTest { @Test(timeout = 2000) @@ -439,4 +442,169 @@ public Observable call() { assertEquals("timeoutSelector is null", ex.getMessage()); } } + + @Test + public void disconnectOnTimeout() { + final List list = Collections.synchronizedList(new ArrayList()); + + final TestScheduler sch = new TestScheduler(); + + Subject subject = PublishSubject.create(); + Observable initialObservable = subject.share() + .map(new Func1() { + @Override + public Long call(Long value) { + list.add("Received value " + value); + return value; + } + }); + + Observable timeoutObservable = initialObservable + .map(new Func1() { + @Override + public Long call(Long value) { + list.add("Timeout received value " + value); + return value; + } + }); + + TestSubscriber subscriber = new TestSubscriber(); + initialObservable + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + list.add("Unsubscribed"); + } + }) + .timeout( + new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, + new Func1>() { + @Override + public Observable call(Long v) { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, + timeoutObservable).subscribe(subscriber); + + subject.onNext(5L); + + sch.advanceTimeBy(2, TimeUnit.SECONDS); + + subject.onNext(10L); + subject.onCompleted(); + + subscriber.awaitTerminalEvent(); + subscriber.assertNoErrors(); + subscriber.assertValues(5L, 10L); + + assertEquals(Arrays.asList( + "Received value 5", + "Unsubscribed", + "Received value 10", + "Timeout received value 10" + ), list); + } + + @Test + public void fallbackIsError() { + final TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.never() + .timeout(new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, + new Func1>() { + @Override + public Observable call(Object v) { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, Observable.error(new TestException())) + .test(); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertFailure(TestException.class); + } + + @Test + public void mainErrors() { + final TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.error(new IOException()) + .timeout(new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, + new Func1>() { + @Override + public Observable call(Object v) { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, Observable.error(new TestException())) + .test(); + + as.assertFailure(IOException.class); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertFailure(IOException.class); + } + + @Test + public void timeoutCompletesWithFallback() { + final TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.never() + .timeout(new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch).ignoreElements(); + } + }, + new Func1>() { + @Override + public Observable call(Object v) { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, Observable.just(1)) + .test(); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertResult(1); + } + + @Test + public void nullItemTimeout() { + final TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.just(1).concatWith(Observable.never()) + .timeout(new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch).ignoreElements(); + } + }, + new Func1>() { + @Override + public Observable call(Object v) { + return null; + } + }, Observable.just(1)) + .test(); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertFailureAndMessage(NullPointerException.class, "The itemTimeoutIndicator returned a null Observable", 1); + } } From 396b6104e419b80002c45faf76ac38f00d2ff64a Mon Sep 17 00:00:00 2001 From: David Karnok Date: Thu, 19 Oct 2017 10:54:04 +0200 Subject: [PATCH 22/37] Release 1.3.3 --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 3a834830b7..b8ac66c635 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # RxJava Releases # +### Version 1.3.3 - October 19, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.3%7C)) + +#### Bugfixes + +- [Pull 5660](https://github.com/ReactiveX/RxJava/pull/5660): Fix `timeout` (timed, selector) unsubscribe bug. + ### Version 1.3.2 - September 15, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.2%7C)) #### Bugfixes From 5b2394c9ee91f298661fff5e043744c84b425808 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Tue, 31 Oct 2017 19:37:55 +0100 Subject: [PATCH 23/37] 1.x: fix Completable.concat to use replace (don't dispose old) (#5696) * 1.x: fix Completable.concat to use replace (don't dispose old) * Remove original issue comments --- .../CompletableOnSubscribeConcatArray.java | 8 +- .../CompletableOnSubscribeConcatIterable.java | 9 ++- .../operators/CompletableConcatTest.java | 79 ++++++++++++++++++- 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java index f291f0f649..cbac07ded5 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java @@ -20,7 +20,7 @@ import rx.*; import rx.Completable.OnSubscribe; -import rx.subscriptions.SerialSubscription; +import rx.internal.subscriptions.SequentialSubscription; public final class CompletableOnSubscribeConcatArray implements OnSubscribe { final Completable[] sources; @@ -45,17 +45,17 @@ static final class ConcatInnerSubscriber extends AtomicInteger implements Comple int index; - final SerialSubscription sd; + final SequentialSubscription sd; public ConcatInnerSubscriber(CompletableSubscriber actual, Completable[] sources) { this.actual = actual; this.sources = sources; - this.sd = new SerialSubscription(); + this.sd = new SequentialSubscription(); } @Override public void onSubscribe(Subscription d) { - sd.set(d); + sd.replace(d); } @Override diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java index e5a3e95fc7..7506286906 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java @@ -21,7 +21,8 @@ import rx.*; import rx.Completable.OnSubscribe; -import rx.subscriptions.*; +import rx.internal.subscriptions.SequentialSubscription; +import rx.subscriptions.Subscriptions; public final class CompletableOnSubscribeConcatIterable implements OnSubscribe { final Iterable sources; @@ -61,17 +62,17 @@ static final class ConcatInnerSubscriber extends AtomicInteger implements Comple final CompletableSubscriber actual; final Iterator sources; - final SerialSubscription sd; + final SequentialSubscription sd; public ConcatInnerSubscriber(CompletableSubscriber actual, Iterator sources) { this.actual = actual; this.sources = sources; - this.sd = new SerialSubscription(); + this.sd = new SequentialSubscription(); } @Override public void onSubscribe(Subscription d) { - sd.set(d); + sd.replace(d); } @Override diff --git a/src/test/java/rx/internal/operators/CompletableConcatTest.java b/src/test/java/rx/internal/operators/CompletableConcatTest.java index d05b11fc45..f55898193c 100644 --- a/src/test/java/rx/internal/operators/CompletableConcatTest.java +++ b/src/test/java/rx/internal/operators/CompletableConcatTest.java @@ -16,7 +16,11 @@ package rx.internal.operators; -import java.util.concurrent.TimeUnit; +import static org.junit.Assert.assertFalse; + +import java.util.Arrays; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.*; @@ -58,4 +62,77 @@ public void call() { Assert.assertEquals(5, calls[0]); } + + @Test + public void andThenNoInterrupt() throws InterruptedException { + for (int k = 0; k < 100; k++) { + final int count = 10; + final CountDownLatch latch = new CountDownLatch(count); + final AtomicBoolean interrupted = new AtomicBoolean(); + + for (int i = 0; i < count; i++) { + Completable.complete() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .andThen(Completable.fromAction(new Action0() { + @Override + public void call() { + try { + Thread.sleep(30); + } catch (InterruptedException e) { + System.out.println("Interrupted! " + Thread.currentThread()); + interrupted.set(true); + } + } + })) + .subscribe(new Action0() { + @Override + public void call() { + latch.countDown(); + } + }); + } + + latch.await(); + assertFalse("The second Completable was interrupted!", interrupted.get()); + } + } + + @Test + public void noInterrupt() throws InterruptedException { + for (int k = 0; k < 100; k++) { + final int count = 10; + final CountDownLatch latch = new CountDownLatch(count); + final AtomicBoolean interrupted = new AtomicBoolean(); + + for (int i = 0; i < count; i++) { + Completable c0 = Completable.fromAction(new Action0() { + @Override + public void call() { + try { + Thread.sleep(30); + } catch (InterruptedException e) { + System.out.println("Interrupted! " + Thread.currentThread()); + interrupted.set(true); + } + } + }); + Completable.concat(Arrays.asList(Completable.complete() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()), + c0) + ) + .subscribe(new Action0() { + @Override + public void call() { + latch.countDown(); + } + }); + } + + latch.await(); + assertFalse("The second Completable was interrupted!", interrupted.get()); + } + } + } From 048175a529f4b29b8c9778d3aecd4989fba4e486 Mon Sep 17 00:00:00 2001 From: Sadegh Date: Sat, 11 Nov 2017 17:00:46 +0330 Subject: [PATCH 24/37] 1.x: Add a sentence to documentation of take() operator (#5719) * Add a sentence to documentation of take() operator * Rephrase java doc for take() method --- src/main/java/rx/Observable.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/rx/Observable.java b/src/main/java/rx/Observable.java index 4b3d7802b4..5d1896008e 100644 --- a/src/main/java/rx/Observable.java +++ b/src/main/java/rx/Observable.java @@ -10536,6 +10536,9 @@ public final Observable take(final int count) { * Returns an Observable that emits those items emitted by source Observable before a specified time runs * out. *

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

* *

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

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

* *

*
Backpressure:
From bd862bc2335fe64190a8503da81c5146b9aadad7 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Thu, 16 Nov 2017 09:36:46 +0100 Subject: [PATCH 25/37] 1.x: fix broken backpressure through unsubscribeOn() (#5726) --- .../operators/OperatorUnsubscribeOn.java | 4 +++ .../operators/OperatorUnsubscribeOnTest.java | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/main/java/rx/internal/operators/OperatorUnsubscribeOn.java b/src/main/java/rx/internal/operators/OperatorUnsubscribeOn.java index 2f72d62d6b..a581009884 100644 --- a/src/main/java/rx/internal/operators/OperatorUnsubscribeOn.java +++ b/src/main/java/rx/internal/operators/OperatorUnsubscribeOn.java @@ -52,6 +52,10 @@ public void onNext(T t) { subscriber.onNext(t); } + @Override + public void setProducer(Producer p) { + subscriber.setProducer(p); + } }; subscriber.add(Subscriptions.create(new Action0() { diff --git a/src/test/java/rx/internal/operators/OperatorUnsubscribeOnTest.java b/src/test/java/rx/internal/operators/OperatorUnsubscribeOnTest.java index fe8d664122..c409a467bf 100644 --- a/src/test/java/rx/internal/operators/OperatorUnsubscribeOnTest.java +++ b/src/test/java/rx/internal/operators/OperatorUnsubscribeOnTest.java @@ -204,4 +204,31 @@ public Thread getThread() { } } + + @Test + public void backpressure() { + AssertableSubscriber as = Observable.range(1, 10) + .unsubscribeOn(Schedulers.trampoline()) + .test(0); + + as.assertNoValues() + .assertNoErrors() + .assertNotCompleted(); + + as.requestMore(1); + + as.assertValue(1) + .assertNoErrors() + .assertNotCompleted(); + + as.requestMore(3); + + as.assertValues(1, 2, 3, 4) + .assertNoErrors() + .assertNotCompleted(); + + as.requestMore(10); + + as.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } } \ No newline at end of file From 265fb484f504e093da508811c498e6241975950e Mon Sep 17 00:00:00 2001 From: David Karnok Date: Sun, 19 Nov 2017 11:40:40 +0100 Subject: [PATCH 26/37] Release 1.3.4 --- CHANGES.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index b8ac66c635..41d2e5b164 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,16 @@ # RxJava Releases # +### Version 1.3.4 - November 19, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.4%7C)) + +#### Bugfixes + +- [Pull 5696](https://github.com/ReactiveX/RxJava/pull/5696): Fix `Completable.concat` to use `replace` and don't dispose the old `Subscription` on switching to the next source. +- [Pull 5726](https://github.com/ReactiveX/RxJava/pull/5726): Fix broken backpressure through `unsubscribeOn()`. + +#### Documentation + +- [Pull 5719](https://github.com/ReactiveX/RxJava/pull/5719): Document the timed `take()` operator will signal the `onCompleted` event on the given `Scheduler` when it times out. + ### Version 1.3.3 - October 19, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.3%7C)) #### Bugfixes From ef329508bc686cd68614da8676bb6b903c82e67a Mon Sep 17 00:00:00 2001 From: David Karnok Date: Thu, 25 Jan 2018 13:39:59 +0100 Subject: [PATCH 27/37] 1.x: Plugin lookup workaround for System.properties access restrictions (#5820) * 1.x: Plugin lookup workaround for System.properties access restrictions * System.getProperties() can also throw SecurityException --- src/main/java/rx/plugins/RxJavaPlugins.java | 54 +++++++++----- .../java/rx/plugins/RxJavaPluginsTest.java | 72 +++++++++++++++++++ 2 files changed, 109 insertions(+), 17 deletions(-) diff --git a/src/main/java/rx/plugins/RxJavaPlugins.java b/src/main/java/rx/plugins/RxJavaPlugins.java index 7a5856d431..cac5b167c6 100644 --- a/src/main/java/rx/plugins/RxJavaPlugins.java +++ b/src/main/java/rx/plugins/RxJavaPlugins.java @@ -105,7 +105,7 @@ public void reset() { public RxJavaErrorHandler getErrorHandler() { if (errorHandler.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaErrorHandler.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaErrorHandler.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default errorHandler.compareAndSet(null, DEFAULT_ERROR_HANDLER); @@ -147,7 +147,7 @@ public void registerErrorHandler(RxJavaErrorHandler impl) { public RxJavaObservableExecutionHook getObservableExecutionHook() { if (observableExecutionHook.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaObservableExecutionHook.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaObservableExecutionHook.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default observableExecutionHook.compareAndSet(null, RxJavaObservableExecutionHookDefault.getInstance()); @@ -189,7 +189,7 @@ public void registerObservableExecutionHook(RxJavaObservableExecutionHook impl) public RxJavaSingleExecutionHook getSingleExecutionHook() { if (singleExecutionHook.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaSingleExecutionHook.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaSingleExecutionHook.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default singleExecutionHook.compareAndSet(null, RxJavaSingleExecutionHookDefault.getInstance()); @@ -232,7 +232,7 @@ public void registerSingleExecutionHook(RxJavaSingleExecutionHook impl) { public RxJavaCompletableExecutionHook getCompletableExecutionHook() { if (completableExecutionHook.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaCompletableExecutionHook.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaCompletableExecutionHook.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default completableExecutionHook.compareAndSet(null, new RxJavaCompletableExecutionHook() { }); @@ -262,6 +262,19 @@ public void registerCompletableExecutionHook(RxJavaCompletableExecutionHook impl } } + /** + * A security manager may prevent accessing the System properties entirely, + * therefore, the SecurityException is turned into an empty properties. + * @return the Properties to use for looking up settings + */ + /* test */ static Properties getSystemPropertiesSafe() { + try { + return System.getProperties(); + } catch (SecurityException ex) { + return new Properties(); + } + } + /* test */ static Object getPluginImplementationViaProperty(Class pluginClass, Properties propsIn) { // Make a defensive clone because traversal may fail with ConcurrentModificationException // if the properties get changed by something outside RxJava. @@ -284,25 +297,32 @@ public void registerCompletableExecutionHook(RxJavaCompletableExecutionHook impl String classSuffix = ".class"; String implSuffix = ".impl"; - for (Map.Entry e : props.entrySet()) { - String key = e.getKey().toString(); - if (key.startsWith(pluginPrefix) && key.endsWith(classSuffix)) { - String value = e.getValue().toString(); + try { + for (Map.Entry e : props.entrySet()) { + String key = e.getKey().toString(); + if (key.startsWith(pluginPrefix) && key.endsWith(classSuffix)) { + String value = e.getValue().toString(); + + if (classSimpleName.equals(value)) { + String index = key.substring(0, key.length() - classSuffix.length()).substring(pluginPrefix.length()); - if (classSimpleName.equals(value)) { - String index = key.substring(0, key.length() - classSuffix.length()).substring(pluginPrefix.length()); + String implKey = pluginPrefix + index + implSuffix; - String implKey = pluginPrefix + index + implSuffix; + implementingClass = props.getProperty(implKey); - implementingClass = props.getProperty(implKey); + if (implementingClass == null) { + throw new IllegalStateException("Implementing class declaration for " + classSimpleName + " missing: " + implKey); + } - if (implementingClass == null) { - throw new IllegalStateException("Implementing class declaration for " + classSimpleName + " missing: " + implKey); + break; } - - break; } } + } catch (SecurityException ex) { + // https://github.com/ReactiveX/RxJava/issues/5819 + // We don't seem to have access to all properties. + // At least print the exception to the console. + ex.printStackTrace(); } } @@ -339,7 +359,7 @@ public void registerCompletableExecutionHook(RxJavaCompletableExecutionHook impl public RxJavaSchedulersHook getSchedulersHook() { if (schedulersHook.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaSchedulersHook.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaSchedulersHook.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default schedulersHook.compareAndSet(null, RxJavaSchedulersHook.getDefaultInstance()); diff --git a/src/test/java/rx/plugins/RxJavaPluginsTest.java b/src/test/java/rx/plugins/RxJavaPluginsTest.java index 1c69ff1389..50d6944a08 100644 --- a/src/test/java/rx/plugins/RxJavaPluginsTest.java +++ b/src/test/java/rx/plugins/RxJavaPluginsTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.mock; +import java.security.Permission; import java.util.*; import java.util.concurrent.TimeUnit; @@ -344,4 +345,75 @@ public void onNext(Object o) { assertEquals(re, errorHandler.e); assertEquals(1, errorHandler.count); } + + @Test + public void systemPropertiesSecurityException() { + assertNull(RxJavaPlugins.getPluginImplementationViaProperty(Object.class, new Properties() { + + private static final long serialVersionUID = -4291534158508255616L; + + @Override + public Set> entrySet() { + return new HashSet>() { + + private static final long serialVersionUID = -7714005655772619143L; + + @Override + public Iterator> iterator() { + return new Iterator>() { + @Override + public boolean hasNext() { + return true; + } + + @Override + public Map.Entry next() { + throw new SecurityException(); + }; + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + @Override + public synchronized Object clone() { + return this; + } + })); + } + + @Test + public void securityManagerDenySystemProperties() { + SecurityManager old = System.getSecurityManager(); + try { + SecurityManager sm = new SecurityManager() { + @Override + public void checkPropertiesAccess() { + throw new SecurityException(); + } + + @Override + public void checkPermission(Permission perm) { + // allow restoring the security manager + } + + @Override + public void checkPermission(Permission perm, Object context) { + // allow restoring the security manager + } + }; + + System.setSecurityManager(sm); + assertTrue(RxJavaPlugins.getSystemPropertiesSafe().isEmpty()); + } finally { + System.setSecurityManager(old); + } + + assertFalse(RxJavaPlugins.getSystemPropertiesSafe().isEmpty()); + } } From a49c49f6646d75aafcdd3b0dea3dc3455e88dfc2 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Sat, 27 Jan 2018 12:28:37 +0100 Subject: [PATCH 28/37] Release 1.3.5 --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 41d2e5b164..8f01a33275 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # RxJava Releases # +### Version 1.3.5 - January 27, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.5%7C)) + +#### Other + +- [Pull 5820](https://github.com/ReactiveX/RxJava/pull/5820): `RxJavaPlugins` lookup workaround for `System.getProperties()` access restrictions. + ### Version 1.3.4 - November 19, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.4%7C)) #### Bugfixes From 2ba8bb2862255ab26c61d9a14ef17b32d2bfc484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ko=C5=82aczkowski?= Date: Thu, 15 Feb 2018 11:15:43 +0100 Subject: [PATCH 29/37] Fix a race condition in OperatorMerge.InnerSubscriber#onError (#5851) * Fix a race condition in OperatorMerge.InnerSubscriber#onError Inner subscriber must queue the error first before setting done, so that after emitLoop() removes the subscriber, emitLoop is guaranteed to notice the error. Otherwise it would be possible that inner subscribers count was 0, and at the same time the error queue was empty. * Add unit test for OperatorMerge.InnerSubscriber#onError race --- .../rx/internal/operators/OperatorMerge.java | 5 +++- .../internal/operators/OperatorMergeTest.java | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/rx/internal/operators/OperatorMerge.java b/src/main/java/rx/internal/operators/OperatorMerge.java index fa30f46b10..a52eee07e9 100644 --- a/src/main/java/rx/internal/operators/OperatorMerge.java +++ b/src/main/java/rx/internal/operators/OperatorMerge.java @@ -847,8 +847,11 @@ public void onNext(T t) { } @Override public void onError(Throwable e) { - done = true; + // Need to queue the error first before setting done, so that after emitLoop() removes the subscriber, + // it is guaranteed to notice the error. Otherwise it would be possible that inner subscribers count was 0, + // and at the same time the error queue was empty. parent.getOrCreateErrorQueue().offer(e); + done = true; parent.emit(); } @Override diff --git a/src/test/java/rx/internal/operators/OperatorMergeTest.java b/src/test/java/rx/internal/operators/OperatorMergeTest.java index 7528cb88d8..bc13673f5e 100644 --- a/src/test/java/rx/internal/operators/OperatorMergeTest.java +++ b/src/test/java/rx/internal/operators/OperatorMergeTest.java @@ -1205,6 +1205,34 @@ public void onNext(Integer t) { assertTrue(latch.await(10, TimeUnit.SECONDS)); } + @Test + public void testConcurrentMergeInnerError() { + for (int i = 0; i < 1000; i++) { + final TestSubscriber ts = TestSubscriber.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + final Exception error = new NullPointerException(); + Action0 action1 = new Action0() { + @Override + public void call() { + ps1.onNext(1); + ps1.onCompleted(); + } + }; + Action0 action2 = new Action0() { + @Override + public void call() { + ps2.onError(error); + } + }; + + Observable.mergeDelayError(ps1, ps2).subscribe(ts); + TestUtil.race(action1, action2); + ts.assertTerminalEvent(); + ts.assertError(error); + } + } + private static Action1 printCount() { return new Action1() { long count; From c40a06f331cfeea8ccca987edaff54a294abc07a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ko=C5=82aczkowski?= Date: Thu, 15 Feb 2018 19:49:26 +0100 Subject: [PATCH 30/37] Fix a race condition that may make OperatorMaterialize emit too many terminal notifications (#5850) * Fix a race condition that may make OperatorMaterialize emit the terminal notification more than once The guards in `OperatorMaterialize.ParentSubscriber#drain` were never working, because `busy` was actually never set to true. Therefore it was possible that the `drain` loop was executed by more than one thread concurrently, which could led to undefined behavior. This fix sets `busy` to true at the entry of `drain`. * Add unit test for race in OperatorMaterialize * Set sudo required in travis config --- .travis.yml | 2 +- .../operators/OperatorMaterialize.java | 1 + .../operators/OperatorMaterializeTest.java | 30 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 751937fa52..e9ae55c471 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: java jdk: - oraclejdk8 -sudo: false +sudo: required # as per http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ git: diff --git a/src/main/java/rx/internal/operators/OperatorMaterialize.java b/src/main/java/rx/internal/operators/OperatorMaterialize.java index c1d4a3b65e..ce9e1be604 100644 --- a/src/main/java/rx/internal/operators/OperatorMaterialize.java +++ b/src/main/java/rx/internal/operators/OperatorMaterialize.java @@ -134,6 +134,7 @@ private void drain() { missed = true; return; } + busy = true; } // drain loop final AtomicLong localRequested = this.requested; diff --git a/src/test/java/rx/internal/operators/OperatorMaterializeTest.java b/src/test/java/rx/internal/operators/OperatorMaterializeTest.java index 437593d313..a55758d25c 100644 --- a/src/test/java/rx/internal/operators/OperatorMaterializeTest.java +++ b/src/test/java/rx/internal/operators/OperatorMaterializeTest.java @@ -29,9 +29,12 @@ import rx.Notification; import rx.Observable; import rx.Subscriber; +import rx.TestUtil; +import rx.functions.Action0; import rx.functions.Action1; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; public class OperatorMaterializeTest { @@ -201,6 +204,33 @@ public void testUnsubscribeJustBeforeCompletionNotificationShouldPreventThatNoti ts.assertUnsubscribed(); } + @Test + public void testConcurrency() { + for (int i = 0; i < 1000; i++) { + final TestSubscriber> ts = TestSubscriber.create(0); + final PublishSubject ps = PublishSubject.create(); + Action0 publishAction = new Action0() { + @Override + public void call() { + ps.onCompleted(); + } + }; + + Action0 requestAction = new Action0() { + @Override + public void call() { + ts.requestMore(1); + } + }; + + ps.materialize().subscribe(ts); + TestUtil.race(publishAction, requestAction); + ts.assertValueCount(1); + ts.assertTerminalEvent(); + ts.assertNoErrors(); + } + } + private static class TestObserver extends Subscriber> { boolean onCompleted; From 6e6c5143afec6cdddf72e4e890a02a683ea14ba3 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Thu, 15 Feb 2018 20:53:01 +0100 Subject: [PATCH 31/37] Release 1.3.6 --- CHANGES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 8f01a33275..792c803ece 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # RxJava Releases # +### Version 1.3.6 - February 15, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.6%7C)) + +#### Bugfixes + +- [Pull 5850](https://github.com/ReactiveX/RxJava/pull/5850): Fix a race condition that may make `OperatorMaterialize` emit the wrong signals. +- [Pull 5851](https://github.com/ReactiveX/RxJava/pull/5851): Fix a race condition in `OperatorMerge.InnerSubscriber#onError` causing incorrect terminal event. + ### Version 1.3.5 - January 27, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.5%7C)) #### Other From e467798b3b712af4161605e5bd57cb16f7e5168e Mon Sep 17 00:00:00 2001 From: Dave Moten Date: Tue, 20 Mar 2018 01:21:40 +1100 Subject: [PATCH 32/37] 1.x: fix and deprecate evicting groupBy and add new overload (#5917) * 1.x: fix bug and deprecate groupBy overload with evictingMapFactory, add new groupBy evicting overload (#5868) * groupBy, do a null check on g because cancel(K) could have cleared the map --- build.gradle | 2 +- src/main/java/rx/Observable.java | 75 ++- .../internal/operators/OperatorGroupBy.java | 24 +- .../operators/OperatorGroupByEvicting.java | 605 ++++++++++++++++++ .../operators/OperatorGroupByTest.java | 228 +++++++ 5 files changed, 928 insertions(+), 6 deletions(-) create mode 100644 src/main/java/rx/internal/operators/OperatorGroupByEvicting.java diff --git a/build.gradle b/build.gradle index 570ceb242f..8d0d2fa087 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ dependencies { testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' - testCompile 'com.google.guava:guava:19.0' + testCompile 'com.google.guava:guava:24.0-jre' testCompile 'com.pushtorefresh.java-private-constructor-checker:checker:1.2.0' perfCompile 'org.openjdk.jmh:jmh-core:1.11.3' diff --git a/src/main/java/rx/Observable.java b/src/main/java/rx/Observable.java index 5d1896008e..99e84ec609 100644 --- a/src/main/java/rx/Observable.java +++ b/src/main/java/rx/Observable.java @@ -7275,7 +7275,7 @@ public final void forEach(final Action1 onNext, final Action1ReactiveX operators documentation: GroupBy */ public final Observable> groupBy(final Func1 keySelector, final Func1 elementSelector) { - return lift(new OperatorGroupBy(keySelector, elementSelector)); + return lift(new OperatorGroupByEvicting(keySelector, elementSelector)); } /** @@ -7334,7 +7334,12 @@ public final Observable> groupBy(final Func1ReactiveX operators documentation: GroupBy * @since 1.3 + * @deprecated since 1.3.7, use {@link #groupBy(Func1, Func1, int, boolean, Func1)} + * instead which uses much less memory. Please take note of the + * usage difference involving the evicting action which now expects + * the value from the map instead of the key. */ + @Deprecated public final Observable> groupBy(final Func1 keySelector, final Func1 elementSelector, final Func1, Map> evictingMapFactory) { if (evictingMapFactory == null) { @@ -7369,6 +7374,72 @@ public final Observable> groupBy(final Func1 + * {@code + * Func1, Map> mapFactory + * = action -> CacheBuilder.newBuilder() + * .maximumSize(1000) + * .expireAfterAccess(12, TimeUnit.HOURS) + * .removalListener(entry -> action.call(entry.getValue())) + * . build().asMap(); + * } + * + * + * @param + * the key type + * @param + * the element type + * @return an {@code Observable} that emits {@link GroupedObservable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source Observable that share that + * key value + * @throws NullPointerException + * if {@code evictingMapFactory} is null + * @see ReactiveX operators documentation: GroupBy + * @since 1.3.7 + */ + @Experimental + public final Observable> groupBy(final Func1 keySelector, + final Func1 elementSelector, int bufferSize, boolean delayError, + final Func1, Map> evictingMapFactory) { + if (evictingMapFactory == null) { + throw new NullPointerException("evictingMapFactory cannot be null"); + } + return lift(new OperatorGroupByEvicting( + keySelector, elementSelector, bufferSize, delayError, evictingMapFactory)); + } + + /** + * Groups the items emitted by an {@code Observable} according to a specified criterion, and emits these + * grouped items as {@link GroupedObservable}s. The emitted {@code GroupedObservable} allows only a single + * {@link Subscriber} during its lifetime and if this {@code Subscriber} unsubscribes before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedObservable} emission. + *

+ * + *

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

+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param keySelector + * a function that extracts the key for each item * @param * the key type * @return an {@code Observable} that emits {@link GroupedObservable}s, each of which corresponds to a @@ -7377,7 +7448,7 @@ public final Observable> groupBy(final Func1ReactiveX operators documentation: GroupBy */ public final Observable> groupBy(final Func1 keySelector) { - return lift(new OperatorGroupBy(keySelector)); + return lift(new OperatorGroupByEvicting(keySelector)); } /** diff --git a/src/main/java/rx/internal/operators/OperatorGroupBy.java b/src/main/java/rx/internal/operators/OperatorGroupBy.java index 8892f0d4d0..5d6ec2f556 100644 --- a/src/main/java/rx/internal/operators/OperatorGroupBy.java +++ b/src/main/java/rx/internal/operators/OperatorGroupBy.java @@ -42,13 +42,16 @@ * the source and group value type * @param * the value type of the groups + * @deprecated + * since 1.3.7, use {@link OperatorGroupByEvicting} instead */ +@Deprecated public final class OperatorGroupBy implements Operator, T> { final Func1 keySelector; final Func1 valueSelector; final int bufferSize; final boolean delayError; - final Func1, Map> mapFactory; + final Func1, Map> mapFactory; //nullable @SuppressWarnings({ "unchecked", "rawtypes" }) public OperatorGroupBy(Func1 keySelector) { @@ -116,6 +119,10 @@ public static final class GroupBySubscriber final int bufferSize; final boolean delayError; final Map> groups; + + // double store the groups to workaround the bug in the + // signature of groupBy with evicting map factory + final Map> groupsCopy; final Queue> queue; final GroupByProducer producer; final Queue evictedKeys; @@ -134,7 +141,7 @@ public static final class GroupBySubscriber volatile boolean done; final AtomicInteger wip; - + public GroupBySubscriber(Subscriber> actual, Func1 keySelector, Func1 valueSelector, int bufferSize, boolean delayError, Func1, Map> mapFactory) { @@ -158,6 +165,7 @@ public GroupBySubscriber(Subscriber> actual, Fun this.evictedKeys = new ConcurrentLinkedQueue(); this.groups = createMap(mapFactory, new EvictionAction(evictedKeys)); } + this.groupsCopy = new ConcurrentHashMap>(); } static class EvictionAction implements Action1 { @@ -211,6 +219,9 @@ public void onNext(T t) { if (!cancelled.get()) { group = GroupedUnicast.createWith(key, bufferSize, this, delayError); groups.put(mapKey, group); + if (evictedKeys != null) { + groupsCopy.put(mapKey, group); + } groupCount.getAndIncrement(); @@ -234,7 +245,9 @@ public void onNext(T t) { if (evictedKeys != null) { K evictedKey; while ((evictedKey = evictedKeys.poll()) != null) { - GroupedUnicast g = groups.get(evictedKey); + GroupedUnicast g = groupsCopy.remove(evictedKey); + // do a null check on g because cancel(K) could have cleared + // the map if (g != null) { g.onComplete(); } @@ -270,6 +283,7 @@ public void onCompleted() { } groups.clear(); if (evictedKeys != null) { + groupsCopy.clear(); evictedKeys.clear(); } @@ -304,6 +318,9 @@ public void cancel(K key) { unsubscribe(); } } + if (evictedKeys != null) { + groupsCopy.remove(mapKey); + } } void drain() { @@ -364,6 +381,7 @@ void errorAll(Subscriber> a, Queue q, Throwab List> list = new ArrayList>(groups.values()); groups.clear(); if (evictedKeys != null) { + groupsCopy.clear(); evictedKeys.clear(); } diff --git a/src/main/java/rx/internal/operators/OperatorGroupByEvicting.java b/src/main/java/rx/internal/operators/OperatorGroupByEvicting.java new file mode 100644 index 0000000000..d02ecf5d39 --- /dev/null +++ b/src/main/java/rx/internal/operators/OperatorGroupByEvicting.java @@ -0,0 +1,605 @@ +/** + * Copyright 2018 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.internal.operators; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.Observable.*; +import rx.exceptions.Exceptions; +import rx.functions.*; +import rx.internal.producers.ProducerArbiter; +import rx.internal.util.*; +import rx.observables.GroupedObservable; +import rx.plugins.RxJavaHooks; +import rx.observers.Subscribers; +import rx.subscriptions.Subscriptions; + +/** + * Groups the items emitted by an Observable according to a specified criterion, and emits these + * grouped items as Observables, one Observable per group. + *

+ * + * + * @param + * the key type + * @param + * the source and group value type + * @param + * the value type of the groups + */ +public final class OperatorGroupByEvicting implements Operator, T>{ + + final Func1 keySelector; + final Func1 valueSelector; + final int bufferSize; + final boolean delayError; + final Func1, Map> mapFactory; //nullable + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public OperatorGroupByEvicting(Func1 keySelector) { + this(keySelector, (Func1)UtilityFunctions.identity(), RxRingBuffer.SIZE, false, null); + } + + public OperatorGroupByEvicting(Func1 keySelector, Func1 valueSelector) { + this(keySelector, valueSelector, RxRingBuffer.SIZE, false, null); + } + + public OperatorGroupByEvicting(Func1 keySelector, Func1 valueSelector, int bufferSize, boolean delayError, Func1, Map> mapFactory) { + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.bufferSize = bufferSize; + this.delayError = delayError; + this.mapFactory = mapFactory; + } + + @SuppressWarnings("unchecked") + @Override + public Subscriber call(Subscriber> child) { + Map> groups; + Queue> evictedGroups; + + if (mapFactory == null) { + evictedGroups = null; + groups = new ConcurrentHashMap>(); + } else { + evictedGroups = new ConcurrentLinkedQueue>(); + Action1 evictionAction = (Action1)(Action1) + new EvictionAction(evictedGroups); + try { + groups = (Map>)(Map) + mapFactory.call((Action1)(Action1) evictionAction); + } catch (Throwable ex) { + //Can reach here because mapFactory.call() may throw + Exceptions.throwOrReport(ex, child); + Subscriber parent2 = Subscribers.empty(); + parent2.unsubscribe(); + return parent2; + } + } + final GroupBySubscriber parent = new GroupBySubscriber( + child, keySelector, valueSelector, bufferSize, delayError, groups, evictedGroups); + + child.add(Subscriptions.create(new Action0() { + @Override + public void call() { + parent.cancel(); + } + })); + + child.setProducer(parent.producer); + + return parent; + } + + public static final class GroupByProducer implements Producer { + final GroupBySubscriber parent; + + public GroupByProducer(GroupBySubscriber parent) { + this.parent = parent; + } + @Override + public void request(long n) { + parent.requestMore(n); + } + } + + public static final class GroupBySubscriber + extends Subscriber { + final Subscriber> actual; + final Func1 keySelector; + final Func1 valueSelector; + final int bufferSize; + final boolean delayError; + final Map> groups; + final Queue> queue; + final GroupByProducer producer; + final Queue> evictedGroups; + + static final Object NULL_KEY = new Object(); + + final ProducerArbiter s; + + final AtomicBoolean cancelled; + + final AtomicLong requested; + + final AtomicInteger groupCount; + + Throwable error; + volatile boolean done; + + final AtomicInteger wip; + + public GroupBySubscriber(Subscriber> actual, Func1 keySelector, + Func1 valueSelector, int bufferSize, boolean delayError, Map> groups, + Queue> evictedGroups) { + this.actual = actual; + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.bufferSize = bufferSize; + this.delayError = delayError; + this.queue = new ConcurrentLinkedQueue>(); + this.s = new ProducerArbiter(); + this.s.request(bufferSize); + this.producer = new GroupByProducer(this); + this.cancelled = new AtomicBoolean(); + this.requested = new AtomicLong(); + this.groupCount = new AtomicInteger(1); + this.wip = new AtomicInteger(); + this.groups = groups; + this.evictedGroups = evictedGroups; + } + + @Override + public void setProducer(Producer s) { + this.s.setProducer(s); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + final Queue> q = this.queue; + final Subscriber> a = this.actual; + + K key; + try { + key = keySelector.call(t); + } catch (Throwable ex) { + unsubscribe(); + errorAll(a, q, ex); + return; + } + + boolean newGroup = false; + @SuppressWarnings("unchecked") + K mapKey = key != null ? key : (K) NULL_KEY; + GroupedUnicast group = groups.get(mapKey); + if (group == null) { + // if the main has been cancelled, stop creating groups + // and skip this value + if (!cancelled.get()) { + group = GroupedUnicast.createWith(key, bufferSize, this, delayError); + groups.put(mapKey, group); + + groupCount.getAndIncrement(); + + newGroup = false; + q.offer(group); + drain(); + } else { + return; + } + } + + V v; + try { + v = valueSelector.call(t); + } catch (Throwable ex) { + unsubscribe(); + errorAll(a, q, ex); + return; + } + + group.onNext(v); + + if (evictedGroups != null) { + GroupedUnicast evictedGroup; + while ((evictedGroup = evictedGroups.poll()) != null) { + evictedGroup.onComplete(); + } + } + + if (newGroup) { + q.offer(group); + drain(); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaHooks.onError(t); + return; + } + error = t; + done = true; + groupCount.decrementAndGet(); + drain(); + } + + @Override + public void onCompleted() { + if (done) { + return; + } + + for (GroupedUnicast e : groups.values()) { + e.onComplete(); + } + groups.clear(); + if (evictedGroups != null) { + evictedGroups.clear(); + } + + done = true; + groupCount.decrementAndGet(); + drain(); + } + + public void requestMore(long n) { + if (n < 0) { + throw new IllegalArgumentException("n >= 0 required but it was " + n); + } + + BackpressureUtils.getAndAddRequest(requested, n); + drain(); + } + + public void cancel() { + // cancelling the main source means we don't want any more groups + // but running groups still require new values + if (cancelled.compareAndSet(false, true)) { + if (groupCount.decrementAndGet() == 0) { + unsubscribe(); + } + } + } + + public void cancel(K key) { + Object mapKey = key != null ? key : NULL_KEY; + if (groups.remove(mapKey) != null) { + if (groupCount.decrementAndGet() == 0) { + unsubscribe(); + } + } + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + + final Queue> q = this.queue; + final Subscriber> a = this.actual; + + for (;;) { + + if (checkTerminated(done, q.isEmpty(), a, q)) { + return; + } + + long r = requested.get(); + boolean unbounded = r == Long.MAX_VALUE; + long e = 0L; + + while (r != 0) { + boolean d = done; + + GroupedObservable t = q.poll(); + + boolean empty = t == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + a.onNext(t); + + r--; + e--; + } + + if (e != 0L) { + if (!unbounded) { + requested.addAndGet(e); + } + s.request(-e); + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + void errorAll(Subscriber> a, Queue q, Throwable ex) { + q.clear(); + List> list = new ArrayList>(groups.values()); + groups.clear(); + if (evictedGroups != null) { + evictedGroups.clear(); + } + + for (GroupedUnicast e : list) { + e.onError(ex); + } + + a.onError(ex); + } + + boolean checkTerminated(boolean d, boolean empty, + Subscriber> a, Queue q) { + if (d) { + Throwable err = error; + if (err != null) { + errorAll(a, q, err); + return true; + } else + if (empty) { + actual.onCompleted(); + return true; + } + } + return false; + } + } + + static class EvictionAction implements Action1> { + + final Queue> evictedGroups; + + EvictionAction(Queue> evictedGroups) { + this.evictedGroups = evictedGroups; + } + + @Override + public void call(GroupedUnicast group) { + evictedGroups.offer(group); + } + } + + static final class GroupedUnicast extends GroupedObservable { + + public static GroupedUnicast createWith(K key, int bufferSize, GroupBySubscriber parent, boolean delayError) { + State state = new State(bufferSize, parent, key, delayError); + return new GroupedUnicast(key, state); + } + + final State state; + + protected GroupedUnicast(K key, State state) { + super(key, state); + this.state = state; + } + + public void onNext(T t) { + state.onNext(t); + } + + public void onError(Throwable e) { + state.onError(e); + } + + public void onComplete() { + state.onComplete(); + } + } + + static final class State extends AtomicInteger implements Producer, Subscription, OnSubscribe { + /** */ + private static final long serialVersionUID = -3852313036005250360L; + + final K key; + final Queue queue; + final GroupBySubscriber parent; + final boolean delayError; + + final AtomicLong requested; + + volatile boolean done; + Throwable error; + + final AtomicBoolean cancelled; + + final AtomicReference> actual; + + final AtomicBoolean once; + + + public State(int bufferSize, GroupBySubscriber parent, K key, boolean delayError) { + this.queue = new ConcurrentLinkedQueue(); + this.parent = parent; + this.key = key; + this.delayError = delayError; + this.cancelled = new AtomicBoolean(); + this.actual = new AtomicReference>(); + this.once = new AtomicBoolean(); + this.requested = new AtomicLong(); + } + + @Override + public void request(long n) { + if (n < 0) { + throw new IllegalArgumentException("n >= required but it was " + n); + } + if (n != 0L) { + BackpressureUtils.getAndAddRequest(requested, n); + drain(); + } + } + + @Override + public boolean isUnsubscribed() { + return cancelled.get(); + } + + @Override + public void unsubscribe() { + if (cancelled.compareAndSet(false, true)) { + if (getAndIncrement() == 0) { + parent.cancel(key); + } + } + } + + @Override + public void call(Subscriber s) { + if (once.compareAndSet(false, true)) { + s.add(this); + s.setProducer(this); + actual.lazySet(s); + drain(); + } else { + s.onError(new IllegalStateException("Only one Subscriber allowed!")); + } + } + + public void onNext(T t) { + if (t == null) { + error = new NullPointerException(); + done = true; + } else { + queue.offer(NotificationLite.next(t)); + } + drain(); + } + + public void onError(Throwable e) { + error = e; + done = true; + drain(); + } + + public void onComplete() { + done = true; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + int missed = 1; + + final Queue q = queue; + final boolean delayError = this.delayError; + Subscriber a = actual.get(); + for (;;) { + if (a != null) { + if (checkTerminated(done, q.isEmpty(), a, delayError)) { + return; + } + + long r = requested.get(); + boolean unbounded = r == Long.MAX_VALUE; + long e = 0; + + while (r != 0L) { + boolean d = done; + Object v = q.poll(); + boolean empty = v == null; + + if (checkTerminated(d, empty, a, delayError)) { + return; + } + + if (empty) { + break; + } + + a.onNext(NotificationLite.getValue(v)); + + r--; + e--; + } + + if (e != 0L) { + if (!unbounded) { + requested.addAndGet(e); + } + parent.s.request(-e); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + if (a == null) { + a = actual.get(); + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, boolean delayError) { + if (cancelled.get()) { + queue.clear(); + parent.cancel(key); + return true; + } + + if (d) { + if (delayError) { + if (empty) { + Throwable e = error; + if (e != null) { + a.onError(e); + } else { + a.onCompleted(); + } + return true; + } + } else { + Throwable e = error; + if (e != null) { + queue.clear(); + a.onError(e); + return true; + } else + if (empty) { + a.onCompleted(); + return true; + } + } + } + + return false; + } + } +} diff --git a/src/test/java/rx/internal/operators/OperatorGroupByTest.java b/src/test/java/rx/internal/operators/OperatorGroupByTest.java index 7fd6ab0a2e..b9c2bc6ece 100644 --- a/src/test/java/rx/internal/operators/OperatorGroupByTest.java +++ b/src/test/java/rx/internal/operators/OperatorGroupByTest.java @@ -39,8 +39,10 @@ import rx.functions.*; import rx.internal.util.*; import rx.observables.GroupedObservable; +import rx.observers.AssertableSubscriber; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; public class OperatorGroupByTest { @@ -2047,4 +2049,230 @@ public Observable call(GroupedObservable v) { assertTrue("" + c, c > 0); assertTrue("" + c, c < 10000); } + + @Test + public void groupByEvictingMapFactoryThrows() { + final RuntimeException ex = new RuntimeException("boo"); + Func1, Map> evictingMapFactory = // + new Func1, Map>() { + + @Override + public Map call(final Action1 notify) { + throw ex; + } + }; + Observable.just(1) + .groupBy(UtilityFunctions.identity(), UtilityFunctions.identity(), 16, true, evictingMapFactory) + .test() + .assertNoValues() + .assertError(ex); + } + + @Test + public void groupByEvictingMapFactoryExpiryCompletesGroupedFlowable() { + final List completed = new CopyOnWriteArrayList(); + Func1, Map> evictingMapFactory = createEvictingMapFactorySynchronousOnly(1); + PublishSubject subject = PublishSubject.create(); + AssertableSubscriber ts = subject + .groupBy(UtilityFunctions.identity(), UtilityFunctions.identity(), 16, true, evictingMapFactory) + .flatMap(addCompletedKey(completed)) + .test(); + subject.onNext(1); + subject.onNext(2); + subject.onNext(3); + ts.assertValues(1, 2, 3) + .assertNoTerminalEvent(); + assertEquals(Arrays.asList(1, 2), completed); + //ensure coverage of the code that clears the evicted queue + subject.onCompleted(); + ts.assertCompleted(); + ts.assertValueCount(3); + } + + private static final Func1 mod5 = new Func1() { + + @Override + public Integer call(Integer n) { + return n % 5; + } + }; + + @Test + public void groupByEvictingMapFactoryWithExpiringGuavaCacheDemonstrationCodeForUseInJavadoc() { + //javadoc will be a version of this using lambdas and without assertions + final List completed = new CopyOnWriteArrayList(); + //size should be less than 5 to notice the effect + Func1, Map> evictingMapFactory = createEvictingMapFactoryGuava(3); + int numValues = 1000; + Observable.range(1, numValues) + .groupBy(mod5, UtilityFunctions.identity(), 16, true, evictingMapFactory) + .flatMap(addCompletedKey(completed)) + .test() + .assertCompleted() + .assertValueCount(numValues); + //the exact eviction behaviour of the guava cache is not specified so we make some approximate tests + assertTrue(completed.size() > numValues * 0.9); + } + + @Test + public void groupByEvictingMapFactoryEvictionQueueClearedOnErrorCoverageOnly() { + Func1, Map> evictingMapFactory = createEvictingMapFactorySynchronousOnly(1); + PublishSubject subject = PublishSubject.create(); + AssertableSubscriber ts = subject + .groupBy(UtilityFunctions.identity(), UtilityFunctions.identity(), 16, true, evictingMapFactory) + .flatMap(new Func1, Observable>() { + @Override + public Observable call(GroupedObservable g) { + return g; + } + }) + .test(); + RuntimeException ex = new RuntimeException(); + //ensure coverage of the code that clears the evicted queue + subject.onError(ex); + ts.assertNoValues() + .assertError(ex); + } + + private static Func1, Observable> addCompletedKey( + final List completed) { + return new Func1, Observable>() { + @Override + public Observable call(final GroupedObservable g) { + return g.doOnCompleted(new Action0() { + @Override + public void call() { + completed.add(g.getKey()); + } + }); + } + }; + } + + //not thread safe + private static final class SingleThreadEvictingHashMap implements Map { + + private final List list = new ArrayList(); + private final Map map = new HashMap(); + private final int maxSize; + private final Action1 evictedListener; + + SingleThreadEvictingHashMap(int maxSize, Action1 evictedListener) { + this.maxSize = maxSize; + this.evictedListener = evictedListener; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public V get(Object key) { + return map.get(key); + } + + @Override + public V put(K key, V value) { + list.remove(key); + V v; + if (maxSize > 0 && list.size() == maxSize) { + //remove first + K k = list.get(0); + list.remove(0); + v = map.remove(k); + } else { + v = null; + } + list.add(key); + V result = map.put(key, value); + if (v != null) { + evictedListener.call(v); + } + return result; + } + + @Override + public V remove(Object key) { + list.remove(key); + return map.remove(key); + } + + @Override + public void putAll(Map m) { + for (Entry entry: m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + list.clear(); + map.clear(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + } + + private static Func1, Map> createEvictingMapFactoryGuava(final int maxSize) { + Func1, Map> evictingMapFactory = // + new Func1, Map>() { + + @Override + public Map call(final Action1 notify) { + return CacheBuilder.newBuilder() // + .maximumSize(maxSize) // + .removalListener(new RemovalListener() { + @Override + public void onRemoval(RemovalNotification notification) { + notify.call(notification.getValue()); + }}) + . build() + .asMap(); + }}; + return evictingMapFactory; + } + + private static Func1, Map> createEvictingMapFactorySynchronousOnly(final int maxSize) { + Func1, Map> evictingMapFactory = // + new Func1, Map>() { + + @Override + public Map call(final Action1 notify) { + return new SingleThreadEvictingHashMap(maxSize, new Action1() { + @Override + public void call(Object object) { + notify.call(object); + }}); + }}; + return evictingMapFactory; + } } From 0641802a4ff9429abc96fb02e926cddcff6ce7e7 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Wed, 21 Mar 2018 17:44:07 +0100 Subject: [PATCH 33/37] Release 1.3.7 --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 792c803ece..40d826708e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # RxJava Releases # +### Version 1.3.7 - March 21, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.7%7C)) + +#### Bugfixes + +- [Pull 5917](https://github.com/ReactiveX/RxJava/pull/5917): Fix and deprecate evicting `groupBy` and add a new overload with the corrected signature. + ### Version 1.3.6 - February 15, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.6%7C)) #### Bugfixes From 3aad9a62ff1db8904812085cb1818eb13f652ecc Mon Sep 17 00:00:00 2001 From: David Karnok Date: Mon, 26 Mar 2018 15:45:52 +0200 Subject: [PATCH 34/37] 1.x: Fix take() to route late errors to RxJavaHooks (#5935) --- .../rx/internal/operators/OperatorTake.java | 3 ++ .../internal/operators/OperatorTakeTest.java | 40 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/main/java/rx/internal/operators/OperatorTake.java b/src/main/java/rx/internal/operators/OperatorTake.java index 0be75b4f2c..d49f155d9d 100644 --- a/src/main/java/rx/internal/operators/OperatorTake.java +++ b/src/main/java/rx/internal/operators/OperatorTake.java @@ -19,6 +19,7 @@ import rx.*; import rx.Observable.Operator; +import rx.plugins.RxJavaHooks; /** * An {@code Observable} that emits the first {@code num} items emitted by the source {@code Observable}. @@ -66,6 +67,8 @@ public void onError(Throwable e) { } finally { unsubscribe(); } + } else { + RxJavaHooks.onError(e); } } diff --git a/src/test/java/rx/internal/operators/OperatorTakeTest.java b/src/test/java/rx/internal/operators/OperatorTakeTest.java index edf17bac10..0885a8b343 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeTest.java @@ -19,7 +19,7 @@ import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; -import java.util.Arrays; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; @@ -27,10 +27,13 @@ import org.mockito.InOrder; import rx.*; +import rx.Observable; import rx.Observable.OnSubscribe; +import rx.Observer; import rx.exceptions.TestException; import rx.functions.*; import rx.observers.*; +import rx.plugins.RxJavaHooks; import rx.schedulers.Schedulers; import rx.subjects.PublishSubject; @@ -457,4 +460,39 @@ public void takeZero() { ts.assertCompleted(); } + @Test + public void crashReportedToHooks() { + final List errors = Collections.synchronizedList(new ArrayList()); + RxJavaHooks.setOnError(new Action1() { + @Override + public void call(Throwable error) { + errors.add(error); + } + }); + + try { + Observable.just("1") + .take(1) + .toSingle() + .subscribe( + new Action1() { + @Override + public void call(String it) { + throw new TestException("bla"); + } + }, + new Action1() { + @Override + public void call(Throwable error) { + errors.add(new AssertionError()); + } + } + ); + + assertEquals("" + errors, 1, errors.size()); + assertTrue("" + errors.get(0), errors.get(0).getMessage().equals("bla")); + } finally { + RxJavaHooks.setOnError(null); + } + } } From 7e3879abfb32eeebb38c970195a7f1e354eb1f82 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Sat, 31 Mar 2018 17:27:03 +0200 Subject: [PATCH 35/37] Release 1.3.8 --- CHANGES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 40d826708e..31299c26f8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ # RxJava Releases # +### Version 1.3.8 - March 31, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.8%7C)) + +RxJava 1.x is now officially **end-of-life (EOL)**. No further developments, bugfixes, enhancements, javadoc changes, maintenance will be provided by this project after version **1.3.8**. + +Users are encourage to [migrate to 2.x](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0). In accordance, the wiki will be updated in the coming months to describe 2.x features and properties. + +#### Bugfixes + +- [Pull 5935](https://github.com/ReactiveX/RxJava/pull/5935): Fix `take()` to route late errors to `RxJavaHooks`. + ### Version 1.3.7 - March 21, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.7%7C)) #### Bugfixes From 2ea5f309484d395efd3cac49a8e4b18e4302d061 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Sat, 31 Mar 2018 17:44:28 +0200 Subject: [PATCH 36/37] 1.x: readme update about EOL --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 3525242ea4..21176e556d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # RxJava: Reactive Extensions for the JVM +## End-of-Life notice + +As of March 31, 2018, The RxJava 1.x branch and version is end-of-life (EOL). No further development, bugfixes, documentation changes, PRs, releases or maintenance will be performed by the project on the 1.x line. + +Users are encouraged to migrate to [2.x](https://github.com/ReactiveX/RxJava) which is currently the only official RxJava version being managed. + +---------------------------------- + [![codecov.io](http://codecov.io/github/ReactiveX/RxJava/coverage.svg?branch=1.x)](http://codecov.io/github/ReactiveX/RxJava?branch=1.x) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.reactivex/rxjava/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.reactivex/rxjava) From a62edb68aa37899320661225ea4944b1b73ab802 Mon Sep 17 00:00:00 2001 From: David Karnok Date: Tue, 2 Mar 2021 12:59:56 +0100 Subject: [PATCH 37/37] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21176e556d..a0eabf5886 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ As of March 31, 2018, The RxJava 1.x branch and version is end-of-life (EOL). No further development, bugfixes, documentation changes, PRs, releases or maintenance will be performed by the project on the 1.x line. -Users are encouraged to migrate to [2.x](https://github.com/ReactiveX/RxJava) which is currently the only official RxJava version being managed. +Users are encouraged to migrate to [3.x](https://github.com/ReactiveX/RxJava) which is currently the only official RxJava version being managed. ----------------------------------