15
15
*/
16
16
package rx .operators ;
17
17
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 ;
21
25
22
26
import java .util .concurrent .CountDownLatch ;
23
27
import java .util .concurrent .TimeUnit ;
28
+ import java .util .concurrent .atomic .AtomicBoolean ;
24
29
import java .util .concurrent .atomic .AtomicInteger ;
25
30
26
31
import org .junit .Test ;
27
32
import org .mockito .InOrder ;
28
33
29
34
import rx .Observable ;
30
- import rx .Observable .OnSubscribeFunc ;
35
+ import rx .Observable .OnSubscribe ;
31
36
import rx .Observer ;
32
37
import rx .Subscriber ;
33
38
import rx .Subscription ;
39
+ import rx .functions .Action0 ;
34
40
import rx .functions .Action1 ;
41
+ import rx .observers .TestSubscriber ;
35
42
import rx .subjects .PublishSubject ;
36
43
import rx .subscriptions .Subscriptions ;
37
44
@@ -112,7 +119,7 @@ public void testInfiniteRetry() {
112
119
inOrder .verifyNoMoreInteractions ();
113
120
}
114
121
115
- public static class FuncWithErrors implements Observable .OnSubscribeFunc <String > {
122
+ public static class FuncWithErrors implements Observable .OnSubscribe <String > {
116
123
117
124
private final int numFailures ;
118
125
private final AtomicInteger count = new AtomicInteger (0 );
@@ -122,15 +129,14 @@ public static class FuncWithErrors implements Observable.OnSubscribeFunc<String>
122
129
}
123
130
124
131
@ Override
125
- public Subscription onSubscribe ( Observer <? super String > o ) {
132
+ public void call ( Subscriber <? super String > o ) {
126
133
o .onNext ("beginningEveryTime" );
127
134
if (count .incrementAndGet () <= numFailures ) {
128
135
o .onError (new RuntimeException ("forced failure: " + count .get ()));
129
136
} else {
130
137
o .onNext ("onSuccessOnly" );
131
138
o .onCompleted ();
132
139
}
133
- return Subscriptions .empty ();
134
140
}
135
141
}
136
142
@@ -150,101 +156,14 @@ public void call(Integer n) {
150
156
assertEquals (1 , count .get ());
151
157
}
152
158
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
-
240
159
@ Test
241
- public void testRetryAllowsSubscriptionAfterAllSubscriptionsUnsubsribed () throws InterruptedException {
160
+ public void testRetryAllowsSubscriptionAfterAllSubscriptionsUnsubscribed () throws InterruptedException {
242
161
final AtomicInteger subsCount = new AtomicInteger (0 );
243
- OnSubscribeFunc <String > onSubscribe = new OnSubscribeFunc <String >() {
162
+ OnSubscribe <String > onSubscribe = new OnSubscribe <String >() {
244
163
@ Override
245
- public Subscription onSubscribe ( Observer <? super String > observer ) {
164
+ public void call ( Subscriber <? super String > s ) {
246
165
subsCount .incrementAndGet ();
247
- return new Subscription () {
166
+ s . add ( new Subscription () {
248
167
boolean unsubscribed = false ;
249
168
250
169
@ Override
@@ -257,7 +176,7 @@ public void unsubscribe() {
257
176
public boolean isUnsubscribed () {
258
177
return unsubscribed ;
259
178
}
260
- };
179
+ }) ;
261
180
}
262
181
};
263
182
Observable <String > stream = Observable .create (onSubscribe );
@@ -269,4 +188,172 @@ public boolean isUnsubscribed() {
269
188
streamWithRetry .subscribe ();
270
189
assertEquals (1 , subsCount .get ());
271
190
}
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
+ }
272
359
}
0 commit comments