35
35
36
36
import java .util .concurrent .atomic .AtomicBoolean ;
37
37
import java .util .concurrent .atomic .AtomicLong ;
38
- import java .util .concurrent .atomic .AtomicReference ;
39
38
40
39
import rx .Notification ;
41
40
import rx .Observable ;
47
46
import rx .functions .Action0 ;
48
47
import rx .functions .Func1 ;
49
48
import rx .functions .Func2 ;
49
+ import rx .internal .producers .ProducerArbiter ;
50
+ import rx .observers .Subscribers ;
50
51
import rx .schedulers .Schedulers ;
51
- import rx .subjects .PublishSubject ;
52
+ import rx .subjects .BehaviorSubject ;
52
53
import rx .subscriptions .SerialSubscription ;
53
54
54
55
public final class OnSubscribeRedo <T > implements OnSubscribe <T > {
55
56
56
- static final Func1 <Observable <? extends Notification <?>>, Observable <?>> REDO_INIFINITE = new Func1 <Observable <? extends Notification <?>>, Observable <?>>() {
57
+ static final Func1 <Observable <? extends Notification <?>>, Observable <?>> REDO_INFINITE = new Func1 <Observable <? extends Notification <?>>, Observable <?>>() {
57
58
@ Override
58
59
public Observable <?> call (Observable <? extends Notification <?>> ts ) {
59
60
return ts .map (new Func1 <Notification <?>, Notification <?>>() {
@@ -120,7 +121,7 @@ public Notification<Integer> call(Notification<Integer> n, Notification<?> term)
120
121
}
121
122
122
123
public static <T > Observable <T > retry (Observable <T > source ) {
123
- return retry (source , REDO_INIFINITE );
124
+ return retry (source , REDO_INFINITE );
124
125
}
125
126
126
127
public static <T > Observable <T > retry (Observable <T > source , final long count ) {
@@ -144,7 +145,7 @@ public static <T> Observable<T> repeat(Observable<T> source) {
144
145
}
145
146
146
147
public static <T > Observable <T > repeat (Observable <T > source , Scheduler scheduler ) {
147
- return repeat (source , REDO_INIFINITE , scheduler );
148
+ return repeat (source , REDO_INFINITE , scheduler );
148
149
}
149
150
150
151
public static <T > Observable <T > repeat (Observable <T > source , final long count ) {
@@ -172,10 +173,10 @@ public static <T> Observable<T> redo(Observable<T> source, Func1<? super Observa
172
173
return create (new OnSubscribeRedo <T >(source , notificationHandler , false , false , scheduler ));
173
174
}
174
175
175
- private Observable <T > source ;
176
+ private final Observable <T > source ;
176
177
private final Func1 <? super Observable <? extends Notification <?>>, ? extends Observable <?>> controlHandlerFunction ;
177
- private boolean stopOnComplete ;
178
- private boolean stopOnError ;
178
+ private final boolean stopOnComplete ;
179
+ private final boolean stopOnError ;
179
180
private final Scheduler scheduler ;
180
181
181
182
private OnSubscribeRedo (Observable <T > source , Func1 <? super Observable <? extends Notification <?>>, ? extends Observable <?>> f , boolean stopOnComplete , boolean stopOnError ,
@@ -189,20 +190,31 @@ private OnSubscribeRedo(Observable<T> source, Func1<? super Observable<? extends
189
190
190
191
@ Override
191
192
public void call (final Subscriber <? super T > child ) {
192
- final AtomicBoolean isLocked = new AtomicBoolean (true );
193
+
194
+ // when true is a marker to say we are ready to resubscribe to source
193
195
final AtomicBoolean resumeBoundary = new AtomicBoolean (true );
196
+
194
197
// incremented when requests are made, decremented when requests are fulfilled
195
198
final AtomicLong consumerCapacity = new AtomicLong (0l );
196
- final AtomicReference <Producer > currentProducer = new AtomicReference <Producer >();
197
199
198
200
final Scheduler .Worker worker = scheduler .createWorker ();
199
201
child .add (worker );
200
202
201
203
final SerialSubscription sourceSubscriptions = new SerialSubscription ();
202
204
child .add (sourceSubscriptions );
203
205
204
- final PublishSubject <Notification <?>> terminals = PublishSubject .create ();
205
-
206
+ // use a subject to receive terminals (onCompleted and onError signals) from
207
+ // the source observable. We use a BehaviorSubject because subscribeToSource
208
+ // may emit a terminal before the restarts observable (transformed terminals)
209
+ // is subscribed
210
+ final BehaviorSubject <Notification <?>> terminals = BehaviorSubject .create ();
211
+ final Subscriber <Notification <?>> dummySubscriber = Subscribers .empty ();
212
+ // subscribe immediately so the last emission will be replayed to the next
213
+ // subscriber (which is the one we care about)
214
+ terminals .subscribe (dummySubscriber );
215
+
216
+ final ProducerArbiter arbiter = new ProducerArbiter ();
217
+
206
218
final Action0 subscribeToSource = new Action0 () {
207
219
@ Override
208
220
public void call () {
@@ -212,11 +224,11 @@ public void call() {
212
224
213
225
Subscriber <T > terminalDelegatingSubscriber = new Subscriber <T >() {
214
226
boolean done ;
227
+
215
228
@ Override
216
229
public void onCompleted () {
217
230
if (!done ) {
218
231
done = true ;
219
- currentProducer .set (null );
220
232
unsubscribe ();
221
233
terminals .onNext (Notification .createOnCompleted ());
222
234
}
@@ -226,7 +238,6 @@ public void onCompleted() {
226
238
public void onError (Throwable e ) {
227
239
if (!done ) {
228
240
done = true ;
229
- currentProducer .set (null );
230
241
unsubscribe ();
231
242
terminals .onNext (Notification .createOnError (e ));
232
243
}
@@ -235,20 +246,30 @@ public void onError(Throwable e) {
235
246
@ Override
236
247
public void onNext (T v ) {
237
248
if (!done ) {
238
- if (consumerCapacity .get () != Long .MAX_VALUE ) {
239
- consumerCapacity .decrementAndGet ();
240
- }
241
249
child .onNext (v );
250
+ decrementConsumerCapacity ();
251
+ arbiter .produced (1 );
252
+ }
253
+ }
254
+
255
+ private void decrementConsumerCapacity () {
256
+ // use a CAS loop because we don't want to decrement the
257
+ // value if it is Long.MAX_VALUE
258
+ while (true ) {
259
+ long cc = consumerCapacity .get ();
260
+ if (cc != Long .MAX_VALUE ) {
261
+ if (consumerCapacity .compareAndSet (cc , cc - 1 )) {
262
+ break ;
263
+ }
264
+ } else {
265
+ break ;
266
+ }
242
267
}
243
268
}
244
269
245
270
@ Override
246
271
public void setProducer (Producer producer ) {
247
- currentProducer .set (producer );
248
- long c = consumerCapacity .get ();
249
- if (c > 0 ) {
250
- producer .request (c );
251
- }
272
+ arbiter .setProducer (producer );
252
273
}
253
274
};
254
275
// new subscription each time so if it unsubscribes itself it does not prevent retries
@@ -278,12 +299,11 @@ public void onError(Throwable e) {
278
299
279
300
@ Override
280
301
public void onNext (Notification <?> t ) {
281
- if (t .isOnCompleted () && stopOnComplete )
282
- child .onCompleted ();
283
- else if (t .isOnError () && stopOnError )
284
- child .onError (t .getThrowable ());
285
- else {
286
- isLocked .set (false );
302
+ if (t .isOnCompleted () && stopOnComplete ) {
303
+ filteredTerminals .onCompleted ();
304
+ } else if (t .isOnError () && stopOnError ) {
305
+ filteredTerminals .onError (t .getThrowable ());
306
+ } else {
287
307
filteredTerminals .onNext (t );
288
308
}
289
309
}
@@ -313,10 +333,15 @@ public void onError(Throwable e) {
313
333
314
334
@ Override
315
335
public void onNext (Object t ) {
316
- if (!isLocked .get () && !child .isUnsubscribed ()) {
336
+ if (!child .isUnsubscribed ()) {
337
+ // perform a best endeavours check on consumerCapacity
338
+ // with the intent of only resubscribing immediately
339
+ // if there is outstanding capacity
317
340
if (consumerCapacity .get () > 0 ) {
318
341
worker .schedule (subscribeToSource );
319
342
} else {
343
+ // set this to true so that on next request
344
+ // subscribeToSource will be scheduled
320
345
resumeBoundary .compareAndSet (false , true );
321
346
}
322
347
}
@@ -334,13 +359,11 @@ public void setProducer(Producer producer) {
334
359
335
360
@ Override
336
361
public void request (final long n ) {
337
- long c = BackpressureUtils .getAndAddRequest (consumerCapacity , n );
338
- Producer producer = currentProducer .get ();
339
- if (producer != null ) {
340
- producer .request (n );
341
- } else
342
- if (c == 0 && resumeBoundary .compareAndSet (true , false )) {
343
- worker .schedule (subscribeToSource );
362
+ if (n > 0 ) {
363
+ BackpressureUtils .getAndAddRequest (consumerCapacity , n );
364
+ arbiter .request (n );
365
+ if (resumeBoundary .compareAndSet (true , false ))
366
+ worker .schedule (subscribeToSource );
344
367
}
345
368
}
346
369
});
0 commit comments