|
13 | 13 |
|
14 | 14 | package io.reactivex.internal.operators.flowable;
|
15 | 15 |
|
16 |
| -import static org.junit.Assert.*; |
17 |
| -import static org.mockito.ArgumentMatchers.*; |
18 |
| -import static org.mockito.Mockito.*; |
| 16 | +import static org.junit.Assert.assertArrayEquals; |
| 17 | +import static org.junit.Assert.assertEquals; |
| 18 | +import static org.junit.Assert.assertNotNull; |
| 19 | +import static org.junit.Assert.assertTrue; |
| 20 | +import static org.mockito.ArgumentMatchers.any; |
| 21 | +import static org.mockito.ArgumentMatchers.anyInt; |
| 22 | +import static org.mockito.Mockito.mock; |
| 23 | +import static org.mockito.Mockito.never; |
| 24 | +import static org.mockito.Mockito.verify; |
19 | 25 |
|
20 | 26 | import java.io.IOException;
|
21 |
| -import java.util.*; |
22 |
| -import java.util.concurrent.*; |
23 |
| -import java.util.concurrent.atomic.*; |
| 27 | +import java.util.ArrayList; |
| 28 | +import java.util.Arrays; |
| 29 | +import java.util.Collection; |
| 30 | +import java.util.HashMap; |
| 31 | +import java.util.List; |
| 32 | +import java.util.Map; |
| 33 | +import java.util.Set; |
| 34 | +import java.util.concurrent.ConcurrentHashMap; |
| 35 | +import java.util.concurrent.ConcurrentLinkedQueue; |
| 36 | +import java.util.concurrent.CopyOnWriteArrayList; |
| 37 | +import java.util.concurrent.CountDownLatch; |
| 38 | +import java.util.concurrent.TimeUnit; |
| 39 | +import java.util.concurrent.atomic.AtomicBoolean; |
| 40 | +import java.util.concurrent.atomic.AtomicInteger; |
| 41 | +import java.util.concurrent.atomic.AtomicReference; |
24 | 42 |
|
25 | 43 | import org.junit.Test;
|
26 | 44 | import org.mockito.Mockito;
|
27 |
| -import org.reactivestreams.*; |
28 |
| - |
29 |
| -import com.google.common.cache.*; |
30 |
| - |
31 |
| -import io.reactivex.*; |
| 45 | +import org.reactivestreams.Publisher; |
| 46 | +import org.reactivestreams.Subscriber; |
| 47 | +import org.reactivestreams.Subscription; |
| 48 | + |
| 49 | +import com.google.common.base.Ticker; |
| 50 | +import com.google.common.cache.CacheBuilder; |
| 51 | +import com.google.common.cache.RemovalListener; |
| 52 | +import com.google.common.cache.RemovalNotification; |
| 53 | + |
| 54 | +import io.reactivex.BackpressureStrategy; |
| 55 | +import io.reactivex.Flowable; |
| 56 | +import io.reactivex.Notification; |
| 57 | +import io.reactivex.Scheduler; |
| 58 | +import io.reactivex.TestHelper; |
32 | 59 | import io.reactivex.exceptions.TestException;
|
33 | 60 | import io.reactivex.flowables.GroupedFlowable;
|
34 |
| -import io.reactivex.functions.*; |
| 61 | +import io.reactivex.functions.Action; |
| 62 | +import io.reactivex.functions.Consumer; |
| 63 | +import io.reactivex.functions.Function; |
| 64 | +import io.reactivex.functions.Predicate; |
35 | 65 | import io.reactivex.internal.functions.Functions;
|
36 | 66 | import io.reactivex.internal.fuseable.QueueFuseable;
|
37 | 67 | import io.reactivex.internal.subscriptions.BooleanSubscription;
|
| 68 | +import io.reactivex.processors.FlowableProcessor; |
38 | 69 | import io.reactivex.processors.PublishProcessor;
|
39 | 70 | import io.reactivex.schedulers.Schedulers;
|
| 71 | +import io.reactivex.schedulers.TestScheduler; |
40 | 72 | import io.reactivex.subjects.PublishSubject;
|
41 |
| -import io.reactivex.subscribers.*; |
| 73 | +import io.reactivex.subscribers.DefaultSubscriber; |
| 74 | +import io.reactivex.subscribers.SubscriberFusion; |
| 75 | +import io.reactivex.subscribers.TestSubscriber; |
42 | 76 |
|
43 | 77 | public class FlowableGroupByTest {
|
44 | 78 |
|
@@ -1932,6 +1966,45 @@ public Publisher<Integer> apply(GroupedFlowable<Integer, Integer> g) throws Exce
|
1932 | 1966 | ts.assertNoValues()
|
1933 | 1967 | .assertError(ex);
|
1934 | 1968 | }
|
| 1969 | + |
| 1970 | + @Test |
| 1971 | + public void testGroupByEvictionCancellationIssue5933() { |
| 1972 | + FlowableProcessor<Integer> source = PublishProcessor.create(); |
| 1973 | + final TestScheduler scheduler = new TestScheduler(); |
| 1974 | + Function<Consumer<Object>, Map<Integer, Object>> mapFactory = createEvictingMapFactorySynchronousOnlyDelayed(2, scheduler, 1, TimeUnit.SECONDS); |
| 1975 | + final AtomicInteger completed = new AtomicInteger(); |
| 1976 | + |
| 1977 | + Flowable<Integer> stream = source |
| 1978 | + .groupBy(Functions.<Integer>identity(), Functions.<Integer>identity(), false, Flowable.bufferSize(), mapFactory) |
| 1979 | + .flatMap(new Function<GroupedFlowable<Integer, Integer>, Publisher<? extends Integer>>() { |
| 1980 | + @Override |
| 1981 | + public Publisher<? extends Integer> apply(GroupedFlowable<Integer, Integer> group) |
| 1982 | + throws Exception { |
| 1983 | + return group |
| 1984 | + .doOnComplete(new Action() { |
| 1985 | + @Override |
| 1986 | + public void run() throws Exception { |
| 1987 | + completed.incrementAndGet(); |
| 1988 | + } |
| 1989 | + }); |
| 1990 | + } |
| 1991 | + }); |
| 1992 | + TestSubscriber<Integer> subscriber = stream.test(); |
| 1993 | + |
| 1994 | + source.onNext(1); |
| 1995 | + source.onNext(2); |
| 1996 | + //this emission will force a completion (eventually) |
| 1997 | + source.onNext(3); |
| 1998 | + assertEquals(0, completed.get()); |
| 1999 | + |
| 2000 | + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); |
| 2001 | + |
| 2002 | + assertEquals(0, completed.get()); |
| 2003 | + |
| 2004 | + subscriber.cancel(); |
| 2005 | + |
| 2006 | + assertEquals(1, completed.get()); |
| 2007 | + } |
1935 | 2008 |
|
1936 | 2009 | private static Function<GroupedFlowable<Integer, Integer>, Publisher<? extends Integer>> addCompletedKey(
|
1937 | 2010 | final List<Integer> completed) {
|
@@ -2086,4 +2159,27 @@ public void accept(Object object) {
|
2086 | 2159 | }};
|
2087 | 2160 | return evictingMapFactory;
|
2088 | 2161 | }
|
| 2162 | + |
| 2163 | + private static Function<Consumer<Object>, Map<Integer, Object>> createEvictingMapFactorySynchronousOnlyDelayed(final int maxSize, final Scheduler scheduler, final int delay, final TimeUnit unit) { |
| 2164 | + Function<Consumer<Object>, Map<Integer, Object>> evictingMapFactory = // |
| 2165 | + new Function<Consumer<Object>, Map<Integer, Object>>() { |
| 2166 | + |
| 2167 | + @Override |
| 2168 | + public Map<Integer, Object> apply(final Consumer<Object> notify) throws Exception { |
| 2169 | + return new SingleThreadEvictingHashMap<Integer,Object>(maxSize, new Consumer<Object>() { |
| 2170 | + @Override |
| 2171 | + public void accept(final Object object) { |
| 2172 | + scheduler.scheduleDirect(new Runnable() { |
| 2173 | + @Override |
| 2174 | + public void run() { |
| 2175 | + try { |
| 2176 | + notify.accept(object); |
| 2177 | + } catch (Exception e) { |
| 2178 | + throw new RuntimeException(e); |
| 2179 | + }} |
| 2180 | + }); |
| 2181 | + }}); |
| 2182 | + }}; |
| 2183 | + return evictingMapFactory; |
| 2184 | + } |
2089 | 2185 | }
|
0 commit comments