3535
3636import java .util .concurrent .atomic .AtomicBoolean ;
3737import java .util .concurrent .atomic .AtomicLong ;
38- import java .util .concurrent .atomic .AtomicReference ;
3938
4039import rx .Notification ;
4140import rx .Observable ;
4746import rx .functions .Action0 ;
4847import rx .functions .Func1 ;
4948import rx .functions .Func2 ;
49+ import rx .internal .producers .ProducerArbiter ;
50+ import rx .observers .Subscribers ;
5051import rx .schedulers .Schedulers ;
51- import rx .subjects .PublishSubject ;
52+ import rx .subjects .BehaviorSubject ;
5253import rx .subscriptions .SerialSubscription ;
5354
5455public final class OnSubscribeRedo <T > implements OnSubscribe <T > {
5556
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 <?>>() {
5758 @ Override
5859 public Observable <?> call (Observable <? extends Notification <?>> ts ) {
5960 return ts .map (new Func1 <Notification <?>, Notification <?>>() {
@@ -120,7 +121,7 @@ public Notification<Integer> call(Notification<Integer> n, Notification<?> term)
120121 }
121122
122123 public static <T > Observable <T > retry (Observable <T > source ) {
123- return retry (source , REDO_INIFINITE );
124+ return retry (source , REDO_INFINITE );
124125 }
125126
126127 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) {
144145 }
145146
146147 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 );
148149 }
149150
150151 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
172173 return create (new OnSubscribeRedo <T >(source , notificationHandler , false , false , scheduler ));
173174 }
174175
175- private Observable <T > source ;
176+ private final Observable <T > source ;
176177 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 ;
179180 private final Scheduler scheduler ;
180181
181182 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
189190
190191 @ Override
191192 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
193195 final AtomicBoolean resumeBoundary = new AtomicBoolean (true );
196+
194197 // incremented when requests are made, decremented when requests are fulfilled
195198 final AtomicLong consumerCapacity = new AtomicLong (0l );
196- final AtomicReference <Producer > currentProducer = new AtomicReference <Producer >();
197199
198200 final Scheduler .Worker worker = scheduler .createWorker ();
199201 child .add (worker );
200202
201203 final SerialSubscription sourceSubscriptions = new SerialSubscription ();
202204 child .add (sourceSubscriptions );
203205
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+
206218 final Action0 subscribeToSource = new Action0 () {
207219 @ Override
208220 public void call () {
@@ -212,11 +224,11 @@ public void call() {
212224
213225 Subscriber <T > terminalDelegatingSubscriber = new Subscriber <T >() {
214226 boolean done ;
227+
215228 @ Override
216229 public void onCompleted () {
217230 if (!done ) {
218231 done = true ;
219- currentProducer .set (null );
220232 unsubscribe ();
221233 terminals .onNext (Notification .createOnCompleted ());
222234 }
@@ -226,7 +238,6 @@ public void onCompleted() {
226238 public void onError (Throwable e ) {
227239 if (!done ) {
228240 done = true ;
229- currentProducer .set (null );
230241 unsubscribe ();
231242 terminals .onNext (Notification .createOnError (e ));
232243 }
@@ -235,20 +246,30 @@ public void onError(Throwable e) {
235246 @ Override
236247 public void onNext (T v ) {
237248 if (!done ) {
238- if (consumerCapacity .get () != Long .MAX_VALUE ) {
239- consumerCapacity .decrementAndGet ();
240- }
241249 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+ }
242267 }
243268 }
244269
245270 @ Override
246271 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 );
252273 }
253274 };
254275 // new subscription each time so if it unsubscribes itself it does not prevent retries
@@ -278,12 +299,11 @@ public void onError(Throwable e) {
278299
279300 @ Override
280301 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 {
287307 filteredTerminals .onNext (t );
288308 }
289309 }
@@ -313,10 +333,15 @@ public void onError(Throwable e) {
313333
314334 @ Override
315335 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
317340 if (consumerCapacity .get () > 0 ) {
318341 worker .schedule (subscribeToSource );
319342 } else {
343+ // set this to true so that on next request
344+ // subscribeToSource will be scheduled
320345 resumeBoundary .compareAndSet (false , true );
321346 }
322347 }
@@ -334,13 +359,11 @@ public void setProducer(Producer producer) {
334359
335360 @ Override
336361 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 );
344367 }
345368 }
346369 });
0 commit comments