1515 */
1616package rx .operators ;
1717
18- import static org .junit .Assert .*;
19- import static org .mockito .Matchers .*;
20- import static org .mockito .Mockito .*;
18+ import static org .junit .Assert .assertEquals ;
19+ import static org .junit .Assert .fail ;
20+ import static org .mockito .Matchers .any ;
21+ import static org .mockito .Mockito .inOrder ;
22+ import static org .mockito .Mockito .mock ;
23+ import static org .mockito .Mockito .never ;
24+ import static org .mockito .Mockito .times ;
2125
2226import java .util .concurrent .CountDownLatch ;
2327import java .util .concurrent .TimeUnit ;
28+ import java .util .concurrent .atomic .AtomicBoolean ;
2429import java .util .concurrent .atomic .AtomicInteger ;
2530
2631import org .junit .Test ;
2732import org .mockito .InOrder ;
2833
2934import rx .Observable ;
30- import rx .Observable .OnSubscribeFunc ;
35+ import rx .Observable .OnSubscribe ;
3136import rx .Observer ;
3237import rx .Subscriber ;
3338import rx .Subscription ;
39+ import rx .functions .Action0 ;
3440import rx .functions .Action1 ;
41+ import rx .observers .TestSubscriber ;
3542import rx .subjects .PublishSubject ;
3643import rx .subscriptions .Subscriptions ;
3744
@@ -112,7 +119,7 @@ public void testInfiniteRetry() {
112119 inOrder .verifyNoMoreInteractions ();
113120 }
114121
115- public static class FuncWithErrors implements Observable .OnSubscribeFunc <String > {
122+ public static class FuncWithErrors implements Observable .OnSubscribe <String > {
116123
117124 private final int numFailures ;
118125 private final AtomicInteger count = new AtomicInteger (0 );
@@ -122,15 +129,14 @@ public static class FuncWithErrors implements Observable.OnSubscribeFunc<String>
122129 }
123130
124131 @ Override
125- public Subscription onSubscribe ( Observer <? super String > o ) {
132+ public void call ( Subscriber <? super String > o ) {
126133 o .onNext ("beginningEveryTime" );
127134 if (count .incrementAndGet () <= numFailures ) {
128135 o .onError (new RuntimeException ("forced failure: " + count .get ()));
129136 } else {
130137 o .onNext ("onSuccessOnly" );
131138 o .onCompleted ();
132139 }
133- return Subscriptions .empty ();
134140 }
135141 }
136142
@@ -150,101 +156,14 @@ public void call(Integer n) {
150156 assertEquals (1 , count .get ());
151157 }
152158
153- public static class SlowFuncAlwaysFails implements Observable .OnSubscribe <String > {
154-
155- final AtomicInteger nextSeq =new AtomicInteger ();
156- final AtomicInteger activeSubs =new AtomicInteger ();
157- final AtomicInteger concurrentSubs =new AtomicInteger ();
158-
159- public void call (final Subscriber <? super String > s )
160- {
161- final int seq =nextSeq .incrementAndGet ();
162-
163- int cur =activeSubs .incrementAndGet ();
164- // Track concurrent subscriptions
165- concurrentSubs .set (Math .max (cur ,concurrentSubs .get ()));
166-
167- // Use async error
168- new Thread (new Runnable () {
169- @ Override
170- public void run () {
171- try {
172- Thread .sleep (100 );
173- } catch (InterruptedException e ) {
174- // ignore
175- }
176- s .onError (new RuntimeException ("Subscriber #" +seq +" fails" ));
177- }
178- }).start ();
179-
180- // Track unsubscribes
181- s .add (new Subscription ()
182- {
183- private boolean active =true ;
184-
185- public void unsubscribe ()
186- {
187- if (active ) {
188- activeSubs .decrementAndGet ();
189- active =false ;
190- }
191- }
192-
193- public boolean isUnsubscribed ()
194- {
195- return !active ;
196- }
197- });
198- }
199- }
200-
201- @ Test
202- public void testUnsubscribeAfterError () {
203-
204- final CountDownLatch check =new CountDownLatch (1 );
205- final SlowFuncAlwaysFails sf =new SlowFuncAlwaysFails ();
206-
207- Observable
208- .create (sf )
209- .retry (4 )
210- .subscribe (
211- new Action1 <String >()
212- {
213- @ Override
214- public void call (String v )
215- {
216- fail ("Should never happen" );
217- }
218- },
219- new Action1 <Throwable >()
220- {
221- public void call (Throwable throwable )
222- {
223- check .countDown ();
224- }
225- }
226- );
227-
228- try
229- {
230- check .await (1 , TimeUnit .SECONDS );
231- } catch (InterruptedException e )
232- {
233- fail ("interrupted" );
234- }
235-
236- assertEquals ("5 Subscribers created" , 5 , sf .nextSeq .get ());
237- assertEquals ("1 Active Subscriber" , 1 , sf .concurrentSubs .get ());
238- }
239-
240159 @ Test
241- public void testRetryAllowsSubscriptionAfterAllSubscriptionsUnsubsribed () throws InterruptedException {
160+ public void testRetryAllowsSubscriptionAfterAllSubscriptionsUnsubscribed () throws InterruptedException {
242161 final AtomicInteger subsCount = new AtomicInteger (0 );
243- OnSubscribeFunc <String > onSubscribe = new OnSubscribeFunc <String >() {
162+ OnSubscribe <String > onSubscribe = new OnSubscribe <String >() {
244163 @ Override
245- public Subscription onSubscribe ( Observer <? super String > observer ) {
164+ public void call ( Subscriber <? super String > s ) {
246165 subsCount .incrementAndGet ();
247- return new Subscription () {
166+ s . add ( new Subscription () {
248167 boolean unsubscribed = false ;
249168
250169 @ Override
@@ -257,7 +176,7 @@ public void unsubscribe() {
257176 public boolean isUnsubscribed () {
258177 return unsubscribed ;
259178 }
260- };
179+ }) ;
261180 }
262181 };
263182 Observable <String > stream = Observable .create (onSubscribe );
@@ -269,4 +188,172 @@ public boolean isUnsubscribed() {
269188 streamWithRetry .subscribe ();
270189 assertEquals (1 , subsCount .get ());
271190 }
191+
192+ @ Test
193+ public void testSourceObservableCallsUnsubscribe () throws InterruptedException {
194+ final AtomicInteger subsCount = new AtomicInteger (0 );
195+
196+ final TestSubscriber <String > ts = new TestSubscriber <String >();
197+
198+ OnSubscribe <String > onSubscribe = new OnSubscribe <String >() {
199+ @ Override
200+ public void call (Subscriber <? super String > s ) {
201+ // if isUnsubscribed is true that means we have a bug such as https://github.com/Netflix/RxJava/issues/1024
202+ if (!s .isUnsubscribed ()) {
203+ subsCount .incrementAndGet ();
204+ s .onError (new RuntimeException ("failed" ));
205+ // it unsubscribes the child directly
206+ // this simulates various error/completion scenarios that could occur
207+ // or just a source that proactively triggers cleanup
208+ s .unsubscribe ();
209+ }
210+ }
211+ };
212+
213+ Observable .create (onSubscribe ).retry (3 ).subscribe (ts );
214+ assertEquals (4 , subsCount .get ()); // 1 + 3 retries
215+ }
216+
217+ class SlowObservable implements Observable .OnSubscribe <Long > {
218+
219+ private AtomicInteger efforts = new AtomicInteger (0 );
220+ private AtomicInteger active = new AtomicInteger (0 ), maxActive = new AtomicInteger (0 );
221+ private AtomicInteger nextBeforeFailure ;
222+
223+ private final int emitDelay ;
224+
225+ public SlowObservable (int emitDelay , int countNext ) {
226+ this .emitDelay = emitDelay ;
227+ this .nextBeforeFailure = new AtomicInteger (countNext );
228+ }
229+
230+ public void call (final Subscriber <? super Long > subscriber ) {
231+ final AtomicBoolean terminate = new AtomicBoolean (false );
232+ efforts .getAndIncrement ();
233+ active .getAndIncrement ();
234+ maxActive .set (Math .max (active .get (), maxActive .get ()));
235+ final Thread thread = new Thread () {
236+ @ Override
237+ public void run () {
238+ long nr = 0 ;
239+ try {
240+ while (!terminate .get ()) {
241+ Thread .sleep (emitDelay );
242+ if (nextBeforeFailure .getAndDecrement () > 0 ) {
243+ subscriber .onNext (nr ++);
244+ }
245+ else {
246+ subscriber .onError (new RuntimeException ("expected-failed" ));
247+ }
248+ }
249+ }
250+ catch (InterruptedException t ) {
251+ }
252+ }
253+ };
254+ thread .start ();
255+ subscriber .add (Subscriptions .create (new Action0 () {
256+ @ Override
257+ public void call () {
258+ terminate .set (true );
259+ active .decrementAndGet ();
260+ }
261+ }));
262+ }
263+ }
264+
265+ /** Observer for listener on seperate thread */
266+ class AsyncObserver <T > implements Observer <T > {
267+
268+ protected CountDownLatch latch = new CountDownLatch (1 );
269+
270+ protected Observer <T > target ;
271+
272+ /** Wrap existing Observer */
273+ public AsyncObserver (Observer <T > target ) {
274+ this .target = target ;
275+ }
276+
277+ /** Wait */
278+ public void await () {
279+ try {
280+ latch .await ();
281+ } catch (InterruptedException e ) {
282+ fail ("Test interrupted" );
283+ }
284+ }
285+
286+ // Observer implementation
287+
288+ @ Override
289+ public void onCompleted () {
290+ target .onCompleted ();
291+ latch .countDown ();
292+ }
293+
294+ @ Override
295+ public void onError (Throwable t ) {
296+ target .onError (t );
297+ latch .countDown ();
298+ }
299+
300+ @ Override
301+ public void onNext (T v ) {
302+ target .onNext (v );
303+ }
304+ }
305+
306+ @ Test (timeout = 1000 )
307+ public void testUnsubscribeAfterError () {
308+
309+ @ SuppressWarnings ("unchecked" )
310+ Observer <Long > observer = mock (Observer .class );
311+
312+ // Observable that always fails after 100ms
313+ SlowObservable so = new SlowObservable (100 , 0 );
314+ Observable <Long > o = Observable
315+ .create (so )
316+ .retry (5 );
317+
318+ AsyncObserver <Long > async = new AsyncObserver <Long >(observer );
319+
320+ o .subscribe (async );
321+
322+ async .await ();
323+
324+ InOrder inOrder = inOrder (observer );
325+ // Should fail once
326+ inOrder .verify (observer , times (1 )).onError (any (Throwable .class ));
327+ inOrder .verify (observer , never ()).onCompleted ();
328+
329+ assertEquals ("Start 6 threads, retry 5 then fail on 6" , 6 , so .efforts .get ());
330+ assertEquals ("Only 1 active subscription" , 1 , so .maxActive .get ());
331+ }
332+
333+ @ Test (timeout = 1000 )
334+ public void testTimeoutWithRetry () {
335+
336+ @ SuppressWarnings ("unchecked" )
337+ Observer <Long > observer = mock (Observer .class );
338+
339+ // Observable that sends every 100ms (timeout fails instead)
340+ SlowObservable so = new SlowObservable (100 , 10 );
341+ Observable <Long > o = Observable
342+ .create (so )
343+ .timeout (80 , TimeUnit .MILLISECONDS )
344+ .retry (5 );
345+
346+ AsyncObserver <Long > async = new AsyncObserver <Long >(observer );
347+
348+ o .subscribe (async );
349+
350+ async .await ();
351+
352+ InOrder inOrder = inOrder (observer );
353+ // Should fail once
354+ inOrder .verify (observer , times (1 )).onError (any (Throwable .class ));
355+ inOrder .verify (observer , never ()).onCompleted ();
356+
357+ assertEquals ("Start 6 threads, retry 5 then fail on 6" , 6 , so .efforts .get ());
358+ }
272359}
0 commit comments