|
1 | 1 | /*
|
2 |
| - * Copyright 2015-2018 the original author or authors. |
| 2 | + * Copyright 2015-present the original author or authors. |
3 | 3 | *
|
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | 5 | * you may not use this file except in compliance with the License.
|
|
16 | 16 |
|
17 | 17 | package io.rsocket.internal;
|
18 | 18 |
|
19 |
| -import io.rsocket.Payload; |
20 |
| -import io.rsocket.util.ByteBufPayload; |
21 |
| -import io.rsocket.util.EmptyPayload; |
22 |
| -import java.util.concurrent.CountDownLatch; |
23 |
| -import org.junit.Assert; |
24 |
| -import org.junit.Test; |
| 19 | +import static org.assertj.core.api.Assertions.assertThat; |
| 20 | + |
| 21 | +import io.netty.buffer.ByteBuf; |
| 22 | +import io.netty.buffer.ByteBufAllocator; |
| 23 | +import io.netty.buffer.Unpooled; |
| 24 | +import io.netty.util.CharsetUtil; |
| 25 | +import io.netty.util.ReferenceCountUtil; |
| 26 | +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; |
| 27 | +import io.rsocket.internal.subscriber.AssertSubscriber; |
| 28 | +import java.time.Duration; |
| 29 | +import org.junit.jupiter.api.BeforeAll; |
| 30 | +import org.junit.jupiter.api.RepeatedTest; |
| 31 | +import org.junit.jupiter.api.Timeout; |
| 32 | +import org.junit.jupiter.params.ParameterizedTest; |
| 33 | +import org.junit.jupiter.params.provider.ValueSource; |
| 34 | +import reactor.core.Fuseable; |
| 35 | +import reactor.core.publisher.Hooks; |
| 36 | +import reactor.core.publisher.Operators; |
| 37 | +import reactor.core.scheduler.Schedulers; |
| 38 | +import reactor.test.StepVerifier; |
| 39 | +import reactor.test.util.RaceTestUtils; |
25 | 40 |
|
26 | 41 | public class UnboundedProcessorTest {
|
27 |
| - @Test |
28 |
| - public void testOnNextBeforeSubscribe_10() { |
29 |
| - testOnNextBeforeSubscribeN(10); |
30 |
| - } |
31 |
| - |
32 |
| - @Test |
33 |
| - public void testOnNextBeforeSubscribe_100() { |
34 |
| - testOnNextBeforeSubscribeN(100); |
35 |
| - } |
36 | 42 |
|
37 |
| - @Test |
38 |
| - public void testOnNextBeforeSubscribe_10_000() { |
39 |
| - testOnNextBeforeSubscribeN(10_000); |
| 43 | + @BeforeAll |
| 44 | + public static void setup() { |
| 45 | + Hooks.onErrorDropped(__ -> {}); |
40 | 46 | }
|
41 | 47 |
|
42 |
| - @Test |
43 |
| - public void testOnNextBeforeSubscribe_100_000() { |
44 |
| - testOnNextBeforeSubscribeN(100_000); |
45 |
| - } |
46 |
| - |
47 |
| - @Test |
48 |
| - public void testOnNextBeforeSubscribe_1_000_000() { |
49 |
| - testOnNextBeforeSubscribeN(1_000_000); |
50 |
| - } |
51 |
| - |
52 |
| - @Test |
53 |
| - public void testOnNextBeforeSubscribe_10_000_000() { |
54 |
| - testOnNextBeforeSubscribeN(10_000_000); |
| 48 | + public static void teardown() { |
| 49 | + Hooks.resetOnErrorDropped(); |
55 | 50 | }
|
56 | 51 |
|
| 52 | + @ParameterizedTest( |
| 53 | + name = |
| 54 | + "Test that emitting {0} onNext before subscribe and requestN should deliver all the signals once the subscriber is available") |
| 55 | + @ValueSource(ints = {10, 100, 10_000, 100_000, 1_000_000, 10_000_000}) |
57 | 56 | public void testOnNextBeforeSubscribeN(int n) {
|
58 |
| - UnboundedProcessor<Payload> processor = new UnboundedProcessor<>(); |
| 57 | + UnboundedProcessor<ByteBuf> processor = new UnboundedProcessor<>(); |
59 | 58 |
|
60 | 59 | for (int i = 0; i < n; i++) {
|
61 |
| - processor.onNext(EmptyPayload.INSTANCE); |
| 60 | + processor.onNext(Unpooled.EMPTY_BUFFER); |
62 | 61 | }
|
63 | 62 |
|
64 | 63 | processor.onComplete();
|
65 | 64 |
|
66 |
| - long count = processor.count().block(); |
67 |
| - |
68 |
| - Assert.assertEquals(n, count); |
69 |
| - } |
70 |
| - |
71 |
| - @Test |
72 |
| - public void testOnNextAfterSubscribe_10() throws Exception { |
73 |
| - testOnNextAfterSubscribeN(10); |
74 |
| - } |
75 |
| - |
76 |
| - @Test |
77 |
| - public void testOnNextAfterSubscribe_100() throws Exception { |
78 |
| - testOnNextAfterSubscribeN(100); |
| 65 | + StepVerifier.create(processor.count()).expectNext(Long.valueOf(n)).verifyComplete(); |
79 | 66 | }
|
80 | 67 |
|
81 |
| - @Test |
82 |
| - public void testOnNextAfterSubscribe_1000() throws Exception { |
83 |
| - testOnNextAfterSubscribeN(1000); |
84 |
| - } |
| 68 | + @ParameterizedTest( |
| 69 | + name = |
| 70 | + "Test that emitting {0} onNext after subscribe and requestN should deliver all the signals") |
| 71 | + @ValueSource(ints = {10, 100, 10_000}) |
| 72 | + public void testOnNextAfterSubscribeN(int n) { |
| 73 | + UnboundedProcessor<ByteBuf> processor = new UnboundedProcessor<>(); |
| 74 | + AssertSubscriber<ByteBuf> assertSubscriber = AssertSubscriber.create(); |
85 | 75 |
|
86 |
| - @Test |
87 |
| - public void testPrioritizedSending() { |
88 |
| - UnboundedProcessor<Payload> processor = new UnboundedProcessor<>(); |
| 76 | + processor.subscribe(assertSubscriber); |
89 | 77 |
|
90 |
| - for (int i = 0; i < 1000; i++) { |
91 |
| - processor.onNext(EmptyPayload.INSTANCE); |
| 78 | + for (int i = 0; i < n; i++) { |
| 79 | + processor.onNext(Unpooled.EMPTY_BUFFER); |
92 | 80 | }
|
93 | 81 |
|
94 |
| - processor.onNextPrioritized(ByteBufPayload.create("test")); |
95 |
| - |
96 |
| - Payload closestPayload = processor.next().block(); |
97 |
| - |
98 |
| - Assert.assertEquals(closestPayload.getDataUtf8(), "test"); |
| 82 | + assertSubscriber.awaitAndAssertNextValueCount(n); |
99 | 83 | }
|
100 | 84 |
|
101 |
| - @Test |
102 |
| - public void testPrioritizedFused() { |
103 |
| - UnboundedProcessor<Payload> processor = new UnboundedProcessor<>(); |
| 85 | + @ParameterizedTest( |
| 86 | + name = |
| 87 | + "Test that prioritized value sending deliver prioritized signals before the others mode[fusionEnabled={0}]") |
| 88 | + @ValueSource(booleans = {true, false}) |
| 89 | + public void testPrioritizedSending(boolean fusedCase) { |
| 90 | + UnboundedProcessor<ByteBuf> processor = new UnboundedProcessor<>(); |
104 | 91 |
|
105 | 92 | for (int i = 0; i < 1000; i++) {
|
106 |
| - processor.onNext(EmptyPayload.INSTANCE); |
| 93 | + processor.onNext(Unpooled.EMPTY_BUFFER); |
107 | 94 | }
|
108 | 95 |
|
109 |
| - processor.onNextPrioritized(ByteBufPayload.create("test")); |
| 96 | + processor.onNextPrioritized(Unpooled.copiedBuffer("test", CharsetUtil.UTF_8)); |
110 | 97 |
|
111 |
| - Payload closestPayload = processor.poll(); |
112 |
| - |
113 |
| - Assert.assertEquals(closestPayload.getDataUtf8(), "test"); |
| 98 | + assertThat(fusedCase ? processor.poll() : processor.next().block()) |
| 99 | + .isNotNull() |
| 100 | + .extracting(bb -> bb.toString(CharsetUtil.UTF_8)) |
| 101 | + .isEqualTo("test"); |
114 | 102 | }
|
115 | 103 |
|
116 |
| - public void testOnNextAfterSubscribeN(int n) throws Exception { |
117 |
| - CountDownLatch latch = new CountDownLatch(n); |
118 |
| - UnboundedProcessor<Payload> processor = new UnboundedProcessor<>(); |
119 |
| - processor.log().doOnNext(integer -> latch.countDown()).subscribe(); |
120 |
| - |
121 |
| - for (int i = 0; i < n; i++) { |
122 |
| - System.out.println("onNexting -> " + i); |
123 |
| - processor.onNext(EmptyPayload.INSTANCE); |
| 104 | + @ParameterizedTest( |
| 105 | + name = |
| 106 | + "Ensures that racing between onNext | dispose | cancel | request(n) will not cause any issues and leaks; mode[fusionEnabled={0}]") |
| 107 | + @ValueSource(booleans = {true, false}) |
| 108 | + public void ensureUnboundedProcessorDisposesQueueProperly(boolean withFusionEnabled) { |
| 109 | + final LeaksTrackingByteBufAllocator allocator = |
| 110 | + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); |
| 111 | + for (int i = 0; i < 100000; i++) { |
| 112 | + final UnboundedProcessor<ByteBuf> unboundedProcessor = new UnboundedProcessor<>(); |
| 113 | + |
| 114 | + final ByteBuf buffer1 = allocator.buffer(1); |
| 115 | + final ByteBuf buffer2 = allocator.buffer(2); |
| 116 | + |
| 117 | + final AssertSubscriber<ByteBuf> assertSubscriber = |
| 118 | + new AssertSubscriber<ByteBuf>(0) |
| 119 | + .requestedFusionMode(withFusionEnabled ? Fuseable.ANY : Fuseable.NONE); |
| 120 | + |
| 121 | + unboundedProcessor.subscribe(assertSubscriber); |
| 122 | + |
| 123 | + RaceTestUtils.race( |
| 124 | + () -> |
| 125 | + RaceTestUtils.race( |
| 126 | + () -> |
| 127 | + RaceTestUtils.race( |
| 128 | + () -> { |
| 129 | + unboundedProcessor.onNext(buffer1); |
| 130 | + unboundedProcessor.onNext(buffer2); |
| 131 | + }, |
| 132 | + unboundedProcessor::dispose, |
| 133 | + Schedulers.elastic()), |
| 134 | + assertSubscriber::cancel, |
| 135 | + Schedulers.elastic()), |
| 136 | + () -> { |
| 137 | + assertSubscriber.request(1); |
| 138 | + assertSubscriber.request(1); |
| 139 | + }, |
| 140 | + Schedulers.elastic()); |
| 141 | + |
| 142 | + assertSubscriber.values().forEach(ReferenceCountUtil::safeRelease); |
| 143 | + |
| 144 | + allocator.assertHasNoLeaks(); |
124 | 145 | }
|
| 146 | + } |
125 | 147 |
|
126 |
| - processor.drain(); |
127 |
| - |
128 |
| - latch.await(); |
| 148 | + @RepeatedTest( |
| 149 | + name = |
| 150 | + "Ensures that racing between onNext + dispose | downstream async drain should not cause any issues and leaks", |
| 151 | + value = 100000) |
| 152 | + @Timeout(60) |
| 153 | + public void ensuresAsyncFusionAndDisposureHasNoDeadlock() { |
| 154 | + // TODO: enable leaks tracking |
| 155 | + // final LeaksTrackingByteBufAllocator allocator = |
| 156 | + // LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); |
| 157 | + final UnboundedProcessor<ByteBuf> unboundedProcessor = new UnboundedProcessor<>(); |
| 158 | + |
| 159 | + // final ByteBuf buffer1 = allocator.buffer(1); |
| 160 | + // final ByteBuf buffer2 = allocator.buffer(2); |
| 161 | + |
| 162 | + final AssertSubscriber<ByteBuf> assertSubscriber = |
| 163 | + new AssertSubscriber<>(Operators.enableOnDiscard(null, ReferenceCountUtil::safeRelease)); |
| 164 | + |
| 165 | + unboundedProcessor.publishOn(Schedulers.parallel()).subscribe(assertSubscriber); |
| 166 | + |
| 167 | + RaceTestUtils.race( |
| 168 | + () -> { |
| 169 | + // unboundedProcessor.onNext(buffer1); |
| 170 | + // unboundedProcessor.onNext(buffer2); |
| 171 | + unboundedProcessor.onNext(Unpooled.EMPTY_BUFFER); |
| 172 | + unboundedProcessor.onNext(Unpooled.EMPTY_BUFFER); |
| 173 | + unboundedProcessor.onNext(Unpooled.EMPTY_BUFFER); |
| 174 | + unboundedProcessor.onNext(Unpooled.EMPTY_BUFFER); |
| 175 | + unboundedProcessor.onNext(Unpooled.EMPTY_BUFFER); |
| 176 | + unboundedProcessor.onNext(Unpooled.EMPTY_BUFFER); |
| 177 | + unboundedProcessor.dispose(); |
| 178 | + }, |
| 179 | + unboundedProcessor::dispose); |
| 180 | + |
| 181 | + assertSubscriber |
| 182 | + .await(Duration.ofSeconds(50)) |
| 183 | + .values() |
| 184 | + .forEach(ReferenceCountUtil::safeRelease); |
| 185 | + |
| 186 | + // allocator.assertHasNoLeaks(); |
129 | 187 | }
|
130 | 188 | }
|
0 commit comments