Skip to content

Commit 13001d0

Browse files
Merge pull request ReactiveX#2627 from akarnokd/FlatMapMaxConcurrent
FlatMap overloads with maximum concurrency parameter
2 parents f85b5cd + 0dadcde commit 13001d0

File tree

2 files changed

+207
-11
lines changed

2 files changed

+207
-11
lines changed

src/main/java/rx/Observable.java

+93-1
Original file line numberDiff line numberDiff line change
@@ -4516,7 +4516,34 @@ public final Observable<T> firstOrDefault(T defaultValue, Func1<? super T, Boole
45164516
public final <R> Observable<R> flatMap(Func1<? super T, ? extends Observable<? extends R>> func) {
45174517
return merge(map(func));
45184518
}
4519-
4519+
4520+
/**
4521+
* Returns an Observable that emits items based on applying a function that you supply to each item emitted
4522+
* by the source Observable, where that function returns an Observable, and then merging those resulting
4523+
* Observables and emitting the results of this merger, while limiting the maximum number of concurrent
4524+
* subscriptions to these Observables.
4525+
* <p>
4526+
* <!-- <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMap.png" alt=""> -->
4527+
* <dl>
4528+
* <dt><b>Scheduler:</b></dt>
4529+
* <dd>{@code flatMap} does not operate by default on a particular {@link Scheduler}.</dd>
4530+
* </dl>
4531+
*
4532+
* @param func
4533+
* a function that, when applied to an item emitted by the source Observable, returns an
4534+
* Observable
4535+
* @param maxConcurrent
4536+
* the maximum number of Observables that may be subscribed to concurrently
4537+
* @return an Observable that emits the result of applying the transformation function to each item emitted
4538+
* by the source Observable and merging the results of the Observables obtained from this
4539+
* transformation
4540+
* @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a>
4541+
*/
4542+
@Beta
4543+
public final <R> Observable<R> flatMap(Func1<? super T, ? extends Observable<? extends R>> func, int maxConcurrent) {
4544+
return merge(map(func), maxConcurrent);
4545+
}
4546+
45204547
/**
45214548
* Returns an Observable that applies a function to each item emitted or notification raised by the source
45224549
* Observable and then flattens the Observables returned from these functions and emits the resulting items.
@@ -4547,6 +4574,40 @@ public final <R> Observable<R> flatMap(
45474574
Func0<? extends Observable<? extends R>> onCompleted) {
45484575
return merge(mapNotification(onNext, onError, onCompleted));
45494576
}
4577+
/**
4578+
* Returns an Observable that applies a function to each item emitted or notification raised by the source
4579+
* Observable and then flattens the Observables returned from these functions and emits the resulting items,
4580+
* while limiting the maximum number of concurrent subscriptions to these Observables.
4581+
* <p>
4582+
* <!-- <img width="640" height="410" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.nce.png" alt=""> -->
4583+
* <dl>
4584+
* <dt><b>Scheduler:</b></dt>
4585+
* <dd>{@code flatMap} does not operate by default on a particular {@link Scheduler}.</dd>
4586+
* </dl>
4587+
*
4588+
* @param <R>
4589+
* the result type
4590+
* @param onNext
4591+
* a function that returns an Observable to merge for each item emitted by the source Observable
4592+
* @param onError
4593+
* a function that returns an Observable to merge for an onError notification from the source
4594+
* Observable
4595+
* @param onCompleted
4596+
* a function that returns an Observable to merge for an onCompleted notification from the source
4597+
* Observable
4598+
* @param maxConcurrent
4599+
* the maximum number of Observables that may be subscribed to concurrently
4600+
* @return an Observable that emits the results of merging the Observables returned from applying the
4601+
* specified functions to the emissions and notifications of the source Observable
4602+
* @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a>
4603+
*/
4604+
@Beta
4605+
public final <R> Observable<R> flatMap(
4606+
Func1<? super T, ? extends Observable<? extends R>> onNext,
4607+
Func1<? super Throwable, ? extends Observable<? extends R>> onError,
4608+
Func0<? extends Observable<? extends R>> onCompleted, int maxConcurrent) {
4609+
return merge(mapNotification(onNext, onError, onCompleted), maxConcurrent);
4610+
}
45504611

45514612
/**
45524613
* Returns an Observable that emits the results of a specified function to the pair of values emitted by the
@@ -4575,6 +4636,37 @@ public final <U, R> Observable<R> flatMap(final Func1<? super T, ? extends Obser
45754636
final Func2<? super T, ? super U, ? extends R> resultSelector) {
45764637
return merge(lift(new OperatorMapPair<T, U, R>(collectionSelector, resultSelector)));
45774638
}
4639+
/**
4640+
* Returns an Observable that emits the results of a specified function to the pair of values emitted by the
4641+
* source Observable and a specified collection Observable, while limiting the maximum number of concurrent
4642+
* subscriptions to these Observables.
4643+
* <p>
4644+
* <!-- <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.r.png" alt=""> -->
4645+
* <dl>
4646+
* <dt><b>Scheduler:</b></dt>
4647+
* <dd>{@code flatMap} does not operate by default on a particular {@link Scheduler}.</dd>
4648+
* </dl>
4649+
*
4650+
* @param <U>
4651+
* the type of items emitted by the collection Observable
4652+
* @param <R>
4653+
* the type of items emitted by the resulting Observable
4654+
* @param collectionSelector
4655+
* a function that returns an Observable for each item emitted by the source Observable
4656+
* @param resultSelector
4657+
* a function that combines one item emitted by each of the source and collection Observables and
4658+
* returns an item to be emitted by the resulting Observable
4659+
* @param maxConcurrent
4660+
* the maximum number of Observables that may be subscribed to concurrently
4661+
* @return an Observable that emits the results of applying a function to a pair of values emitted by the
4662+
* source Observable and the collection Observable
4663+
* @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a>
4664+
*/
4665+
@Beta
4666+
public final <U, R> Observable<R> flatMap(final Func1<? super T, ? extends Observable<? extends U>> collectionSelector,
4667+
final Func2<? super T, ? super U, ? extends R> resultSelector, int maxConcurrent) {
4668+
return merge(lift(new OperatorMapPair<T, U, R>(collectionSelector, resultSelector)), maxConcurrent);
4669+
}
45784670

45794671
/**
45804672
* Returns an Observable that merges each item emitted by the source Observable with the values in an

src/test/java/rx/internal/operators/OperatorFlatMapTest.java

+114-10
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,20 @@
1616
package rx.internal.operators;
1717

1818
import static org.mockito.Matchers.any;
19-
import static org.mockito.Mockito.mock;
20-
import static org.mockito.Mockito.never;
21-
import static org.mockito.Mockito.times;
22-
import static org.mockito.Mockito.verify;
19+
import static org.mockito.Mockito.*;
2320

24-
import java.util.Arrays;
25-
import java.util.List;
21+
import java.util.*;
22+
import java.util.concurrent.TimeUnit;
23+
import java.util.concurrent.atomic.AtomicInteger;
2624

27-
import org.junit.Test;
25+
import org.junit.*;
2826

2927
import rx.Observable;
3028
import rx.Observer;
3129
import rx.exceptions.TestException;
32-
import rx.functions.Func0;
33-
import rx.functions.Func1;
34-
import rx.functions.Func2;
30+
import rx.functions.*;
31+
import rx.observers.TestSubscriber;
32+
import rx.schedulers.Schedulers;
3533

3634
public class OperatorFlatMapTest {
3735
@Test
@@ -312,4 +310,110 @@ public void testFlatMapTransformsMergeException() {
312310
verify(o, never()).onNext(any());
313311
verify(o, never()).onCompleted();
314312
}
313+
314+
private static <T> Observable<T> compose(Observable<T> source, final AtomicInteger subscriptionCount, final int m) {
315+
return source.doOnSubscribe(new Action0() {
316+
@Override
317+
public void call() {
318+
if (subscriptionCount.getAndIncrement() >= m) {
319+
Assert.fail("Too many subscriptions! " + subscriptionCount.get());
320+
}
321+
}
322+
}).doOnCompleted(new Action0() {
323+
@Override
324+
public void call() {
325+
if (subscriptionCount.decrementAndGet() < 0) {
326+
Assert.fail("Too many unsubscriptionss! " + subscriptionCount.get());
327+
}
328+
}
329+
});
330+
}
331+
332+
@Test
333+
public void testFlatMapMaxConcurrent() {
334+
final int m = 4;
335+
final AtomicInteger subscriptionCount = new AtomicInteger();
336+
Observable<Integer> source = Observable.range(1, 10).flatMap(new Func1<Integer, Observable<Integer>>() {
337+
@Override
338+
public Observable<Integer> call(Integer t1) {
339+
return compose(Observable.range(t1 * 10, 2), subscriptionCount, m)
340+
.subscribeOn(Schedulers.computation());
341+
}
342+
}, m);
343+
344+
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
345+
346+
source.subscribe(ts);
347+
348+
ts.awaitTerminalEvent();
349+
ts.assertNoErrors();
350+
Set<Integer> expected = new HashSet<Integer>(Arrays.asList(
351+
10, 11, 20, 21, 30, 31, 40, 41, 50, 51, 60, 61, 70, 71, 80, 81, 90, 91, 100, 101
352+
));
353+
Assert.assertEquals(expected.size(), ts.getOnNextEvents().size());
354+
Assert.assertTrue(expected.containsAll(ts.getOnNextEvents()));
355+
}
356+
@Test
357+
public void testFlatMapSelectorMaxConcurrent() {
358+
final int m = 4;
359+
final AtomicInteger subscriptionCount = new AtomicInteger();
360+
Observable<Integer> source = Observable.range(1, 10).flatMap(new Func1<Integer, Observable<Integer>>() {
361+
@Override
362+
public Observable<Integer> call(Integer t1) {
363+
return compose(Observable.range(t1 * 10, 2), subscriptionCount, m)
364+
.subscribeOn(Schedulers.computation());
365+
}
366+
}, new Func2<Integer, Integer, Integer>() {
367+
@Override
368+
public Integer call(Integer t1, Integer t2) {
369+
return t1 * 1000 + t2;
370+
}
371+
}, m);
372+
373+
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
374+
375+
source.subscribe(ts);
376+
377+
ts.awaitTerminalEvent();
378+
ts.assertNoErrors();
379+
Set<Integer> expected = new HashSet<Integer>(Arrays.asList(
380+
1010, 1011, 2020, 2021, 3030, 3031, 4040, 4041, 5050, 5051,
381+
6060, 6061, 7070, 7071, 8080, 8081, 9090, 9091, 10100, 10101
382+
));
383+
Assert.assertEquals(expected.size(), ts.getOnNextEvents().size());
384+
System.out.println("--> testFlatMapSelectorMaxConcurrent: " + ts.getOnNextEvents());
385+
Assert.assertTrue(expected.containsAll(ts.getOnNextEvents()));
386+
}
387+
@Test
388+
public void testFlatMapTransformsMaxConcurrentNormal() {
389+
final int m = 2;
390+
final AtomicInteger subscriptionCount = new AtomicInteger();
391+
Observable<Integer> onNext =
392+
compose(Observable.from(Arrays.asList(1, 2, 3)).observeOn(Schedulers.computation()), subscriptionCount, m)
393+
.subscribeOn(Schedulers.computation());
394+
Observable<Integer> onCompleted = compose(Observable.from(Arrays.asList(4)), subscriptionCount, m)
395+
.subscribeOn(Schedulers.computation());
396+
Observable<Integer> onError = Observable.from(Arrays.asList(5));
397+
398+
Observable<Integer> source = Observable.from(Arrays.asList(10, 20, 30));
399+
400+
@SuppressWarnings("unchecked")
401+
Observer<Object> o = mock(Observer.class);
402+
TestSubscriber<Object> ts = new TestSubscriber<Object>(o);
403+
404+
source.flatMap(just(onNext), just(onError), just0(onCompleted), m).subscribe(ts);
405+
406+
ts.awaitTerminalEvent(1, TimeUnit.SECONDS);
407+
ts.assertNoErrors();
408+
ts.assertTerminalEvent();
409+
410+
verify(o, times(3)).onNext(1);
411+
verify(o, times(3)).onNext(2);
412+
verify(o, times(3)).onNext(3);
413+
verify(o).onNext(4);
414+
verify(o).onCompleted();
415+
416+
verify(o, never()).onNext(5);
417+
verify(o, never()).onError(any(Throwable.class));
418+
}
315419
}

0 commit comments

Comments
 (0)