16
16
package rx .internal .operators ;
17
17
18
18
import java .util .ArrayList ;
19
+ import java .util .Collection ;
19
20
import java .util .List ;
20
- import java .util .concurrent .atomic .AtomicInteger ;
21
+ import java .util .concurrent .ConcurrentLinkedQueue ;
22
+ import java .util .concurrent .atomic .AtomicReference ;
21
23
22
24
import rx .Observable ;
23
25
import rx .Observable .OnSubscribe ;
26
+ import rx .Producer ;
24
27
import rx .Subscriber ;
28
+ import rx .functions .Action0 ;
29
+ import rx .subscriptions .Subscriptions ;
25
30
26
31
/**
27
32
* Given multiple {@link Observable}s, propagates the one that first emits an item.
@@ -262,22 +267,23 @@ public static <T> OnSubscribe<T> amb(final Iterable<? extends Observable<? exten
262
267
263
268
private static final class AmbSubscriber <T > extends Subscriber <T > {
264
269
265
- private static final int NONE = -1 ;
266
-
267
270
private final Subscriber <? super T > subscriber ;
268
- private final int index ;
269
- private final AtomicInteger choice ;
271
+ private final Selection <T > selection ;
270
272
271
- private AmbSubscriber (Subscriber <? super T > subscriber , int index , AtomicInteger choice ) {
273
+ private AmbSubscriber (long requested , Subscriber <? super T > subscriber , Selection < T > selection ) {
272
274
this .subscriber = subscriber ;
273
- this .choice = choice ;
274
- this .index = index ;
275
+ this .selection = selection ;
276
+ // initial request
277
+ request (requested );
278
+ }
279
+
280
+ private final void requestMore (long n ) {
281
+ request (n );
275
282
}
276
283
277
284
@ Override
278
285
public void onNext (T args ) {
279
286
if (!isSelected ()) {
280
- unsubscribe ();
281
287
return ;
282
288
}
283
289
subscriber .onNext (args );
@@ -286,7 +292,6 @@ public void onNext(T args) {
286
292
@ Override
287
293
public void onCompleted () {
288
294
if (!isSelected ()) {
289
- unsubscribe ();
290
295
return ;
291
296
}
292
297
subscriber .onCompleted ();
@@ -295,44 +300,102 @@ public void onCompleted() {
295
300
@ Override
296
301
public void onError (Throwable e ) {
297
302
if (!isSelected ()) {
298
- unsubscribe ();
299
303
return ;
300
304
}
301
305
subscriber .onError (e );
302
306
}
303
307
304
308
private boolean isSelected () {
305
- int ch = choice .get ();
306
- if (ch == NONE ) {
307
- return choice .compareAndSet (NONE , index );
309
+ if (selection .choice .get () == this ) {
310
+ // fast-path
311
+ return true ;
312
+ } else {
313
+ if (selection .choice .compareAndSet (null , this )) {
314
+ selection .unsubscribeOthers (this );
315
+ return true ;
316
+ } else {
317
+ // we lost so unsubscribe ... and force cleanup again due to possible race conditions
318
+ selection .unsubscribeLosers ();
319
+ return false ;
320
+ }
321
+ }
322
+ }
323
+ }
324
+
325
+ private static class Selection <T > {
326
+ final AtomicReference <AmbSubscriber <T >> choice = new AtomicReference <AmbSubscriber <T >>();
327
+ final Collection <AmbSubscriber <T >> ambSubscribers = new ConcurrentLinkedQueue <AmbSubscriber <T >>();
328
+
329
+ public void unsubscribeLosers () {
330
+ AmbSubscriber <T > winner = choice .get ();
331
+ if (winner != null ) {
332
+ unsubscribeOthers (winner );
333
+ }
334
+ }
335
+
336
+ public void unsubscribeOthers (AmbSubscriber <T > notThis ) {
337
+ for (AmbSubscriber <T > other : ambSubscribers ) {
338
+ if (other != notThis ) {
339
+ other .unsubscribe ();
340
+ }
308
341
}
309
- return ch == index ;
342
+ ambSubscribers . clear () ;
310
343
}
344
+
311
345
}
312
346
313
347
private final Iterable <? extends Observable <? extends T >> sources ;
348
+ private final Selection <T > selection = new Selection <T >();
314
349
315
350
private OnSubscribeAmb (Iterable <? extends Observable <? extends T >> sources ) {
316
351
this .sources = sources ;
317
352
}
318
353
319
354
@ Override
320
- public void call (Subscriber <? super T > subscriber ) {
321
- AtomicInteger choice = new AtomicInteger (AmbSubscriber .NONE );
322
- int index = 0 ;
323
- for (Observable <? extends T > source : sources ) {
324
- if (subscriber .isUnsubscribed ()) {
325
- break ;
355
+ public void call (final Subscriber <? super T > subscriber ) {
356
+ subscriber .add (Subscriptions .create (new Action0 () {
357
+
358
+ @ Override
359
+ public void call () {
360
+ if (selection .choice .get () != null ) {
361
+ // there is a single winner so we unsubscribe it
362
+ selection .choice .get ().unsubscribe ();
363
+ }
364
+ // if we are racing with others still existing, we'll also unsubscribe them
365
+ if (!selection .ambSubscribers .isEmpty ()) {
366
+ for (AmbSubscriber <T > other : selection .ambSubscribers ) {
367
+ other .unsubscribe ();
368
+ }
369
+ selection .ambSubscribers .clear ();
370
+ }
326
371
}
327
- if (choice .get () != AmbSubscriber .NONE ) {
328
- // Already choose someone, the rest Observables can be skipped.
329
- break ;
372
+
373
+ }));
374
+ subscriber .setProducer (new Producer () {
375
+
376
+ @ Override
377
+ public void request (long n ) {
378
+ if (selection .choice .get () != null ) {
379
+ // propagate the request to that single Subscriber that won
380
+ selection .choice .get ().requestMore (n );
381
+ } else {
382
+ for (Observable <? extends T > source : sources ) {
383
+ if (subscriber .isUnsubscribed ()) {
384
+ break ;
385
+ }
386
+ AmbSubscriber <T > ambSubscriber = new AmbSubscriber <T >(n , subscriber , selection );
387
+ selection .ambSubscribers .add (ambSubscriber );
388
+ // possible race condition in previous lines ... a choice may have been made so double check (instead of synchronizing)
389
+ if (selection .choice .get () != null ) {
390
+ // Already chose one, the rest can be skipped and we can clean up
391
+ selection .unsubscribeOthers (selection .choice .get ());
392
+ break ;
393
+ }
394
+ source .unsafeSubscribe (ambSubscriber );
395
+ }
396
+ }
330
397
}
331
- AmbSubscriber <T > ambSubscriber = new AmbSubscriber <T >(subscriber , index , choice );
332
- subscriber .add (ambSubscriber );
333
- source .unsafeSubscribe (ambSubscriber );
334
- index ++;
335
- }
398
+ });
336
399
}
337
400
338
401
}
0 commit comments