1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.testsuite.transport.socket;
17
18 import io.netty.bootstrap.Bootstrap;
19 import io.netty.bootstrap.ServerBootstrap;
20 import io.netty.buffer.ByteBuf;
21 import io.netty.buffer.CompositeByteBuf;
22 import io.netty.buffer.Unpooled;
23 import io.netty.channel.Channel;
24 import io.netty.channel.ChannelFuture;
25 import io.netty.channel.ChannelHandlerContext;
26 import io.netty.channel.ChannelInitializer;
27 import io.netty.channel.ChannelOption;
28 import io.netty.channel.ChannelPromise;
29 import io.netty.channel.SimpleChannelInboundHandler;
30 import io.netty.testsuite.util.TestUtils;
31 import io.netty.util.concurrent.ImmediateEventExecutor;
32 import io.netty.util.concurrent.Promise;
33 import io.netty.util.internal.PlatformDependent;
34 import io.netty.util.internal.StringUtil;
35 import org.junit.jupiter.api.AfterAll;
36 import org.junit.jupiter.api.Test;
37 import org.junit.jupiter.api.TestInfo;
38 import org.junit.jupiter.api.Timeout;
39 import org.opentest4j.TestAbortedException;
40
41 import java.io.IOException;
42 import java.util.Random;
43 import java.util.SplittableRandom;
44 import java.util.concurrent.TimeUnit;
45 import java.util.concurrent.atomic.AtomicInteger;
46 import java.util.concurrent.atomic.AtomicReference;
47
48 import static io.netty.buffer.Unpooled.compositeBuffer;
49 import static io.netty.buffer.Unpooled.wrappedBuffer;
50 import static io.netty.testsuite.transport.TestsuitePermutation.randomBufferType;
51 import static org.junit.jupiter.api.Assertions.assertEquals;
52 import static org.junit.jupiter.api.Assertions.assertNotEquals;
53 import static org.junit.jupiter.api.Assertions.assertSame;
54 import static org.junit.jupiter.api.Assertions.assertTrue;
55
56 public class SocketGatheringWriteTest extends AbstractSocketTest {
57 private static final long TIMEOUT = 120000;
58
59 private static final Random random = new Random();
60 static final byte[] data = new byte[1048576];
61
62 static {
63 PlatformDependent.splittableRandomNextBytes(new SplittableRandom(random.nextLong()), data);
64 }
65
66 @AfterAll
67 public static void compressHeapDumps() throws Exception {
68 TestUtils.compressHeapDumps();
69 }
70
71 @Test
72 @Timeout(value = TIMEOUT, unit = TimeUnit.MILLISECONDS)
73 public void testGatheringWrite(TestInfo testInfo) throws Throwable {
74 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
75 @Override
76 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
77 testGatheringWrite(serverBootstrap, bootstrap);
78 }
79 });
80 }
81
82 public void testGatheringWrite(ServerBootstrap sb, Bootstrap cb) throws Throwable {
83 testGatheringWrite0(sb, cb, data, false, true);
84 }
85
86 @Test
87 @Timeout(value = TIMEOUT, unit = TimeUnit.MILLISECONDS)
88 public void testGatheringWriteNotAutoRead(TestInfo testInfo) throws Throwable {
89 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
90 @Override
91 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
92 testGatheringWriteNotAutoRead(serverBootstrap, bootstrap);
93 }
94 });
95 }
96
97 public void testGatheringWriteNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable {
98 testGatheringWrite0(sb, cb, data, false, false);
99 }
100
101 @Test
102 @Timeout(value = TIMEOUT, unit = TimeUnit.MILLISECONDS)
103 public void testGatheringWriteWithComposite(TestInfo testInfo) throws Throwable {
104 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
105 @Override
106 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
107 testGatheringWriteWithComposite(serverBootstrap, bootstrap);
108 }
109 });
110 }
111
112 public void testGatheringWriteWithComposite(ServerBootstrap sb, Bootstrap cb) throws Throwable {
113 testGatheringWrite0(sb, cb, data, true, true);
114 }
115
116 @Test
117 @Timeout(value = TIMEOUT, unit = TimeUnit.MILLISECONDS)
118 public void testGatheringWriteWithCompositeNotAutoRead(TestInfo testInfo) throws Throwable {
119 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
120 @Override
121 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
122 testGatheringWriteWithCompositeNotAutoRead(serverBootstrap, bootstrap);
123 }
124 });
125 }
126
127 public void testGatheringWriteWithCompositeNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable {
128 testGatheringWrite0(sb, cb, data, true, false);
129 }
130
131
132 @Test
133 @Timeout(value = TIMEOUT, unit = TimeUnit.MILLISECONDS)
134 public void testGatheringWriteBig(TestInfo testInfo) throws Throwable {
135 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
136 @Override
137 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
138 testGatheringWriteBig(serverBootstrap, bootstrap);
139 }
140 });
141 }
142
143 public void testGatheringWriteBig(ServerBootstrap sb, Bootstrap cb) throws Throwable {
144 SplittableRandom rng = new SplittableRandom(random.nextLong());
145 byte[] bigData = new byte[1024 * 1024 * 50];
146 PlatformDependent.splittableRandomNextBytes(rng, bigData);
147 testGatheringWrite0(sb, cb, bigData, false, true);
148 }
149
150 private void testGatheringWrite0(
151 ServerBootstrap sb, Bootstrap cb, byte[] data, boolean composite, boolean autoRead) throws Throwable {
152 sb.childOption(ChannelOption.AUTO_READ, autoRead);
153 cb.option(ChannelOption.AUTO_READ, autoRead);
154
155 Promise<Void> serverDonePromise = ImmediateEventExecutor.INSTANCE.newPromise();
156 final TestServerHandler sh = new TestServerHandler(autoRead, serverDonePromise, data.length);
157 final TestHandler ch = new TestHandler(autoRead);
158
159 cb.handler(ch);
160 sb.childHandler(sh);
161
162 Channel sc = sb.bind().sync().channel();
163 Channel cc = cb.connect(sc.localAddress()).sync().channel();
164
165 SplittableRandom rng = new SplittableRandom(random.nextLong());
166 for (int i = 0; i < data.length;) {
167 int length = Math.min(rng.nextInt(1024 * 8), data.length - i);
168 if (composite && i % 2 == 0) {
169 int firstBufLength = length / 2;
170 CompositeByteBuf comp = compositeBuffer();
171 comp.addComponent(true,
172 randomBufferType(cc.alloc(), data, i, firstBufLength))
173 .addComponent(true,
174 randomBufferType(cc.alloc(), data, i + firstBufLength, length - firstBufLength));
175 cc.write(comp);
176 } else {
177 cc.write(randomBufferType(cc.alloc(), data, i, length));
178 }
179 i += length;
180 }
181
182 ChannelFuture cf = cc.writeAndFlush(Unpooled.EMPTY_BUFFER);
183 assertNotEquals(cc.voidPromise(), cf);
184 try {
185 assertTrue(cf.await(60000));
186 cf.sync();
187 } catch (Throwable t) {
188
189 TestUtils.dump(StringUtil.simpleClassName(this));
190 throw t;
191 }
192
193 serverDonePromise.sync();
194 sh.channel.close().sync();
195 ch.channel.close().sync();
196 sc.close().sync();
197
198 if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) {
199 throw sh.exception.get();
200 }
201 if (sh.exception.get() != null) {
202 throw sh.exception.get();
203 }
204 if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) {
205 throw ch.exception.get();
206 }
207 if (ch.exception.get() != null) {
208 throw ch.exception.get();
209 }
210 ByteBuf expected = wrappedBuffer(data);
211 assertEquals(expected, sh.received);
212 expected.release();
213 sh.received.release();
214 }
215
216 @Test
217 @Timeout(value = 30, unit = TimeUnit.SECONDS)
218 public void testGatheringWriteSameEventLoop(TestInfo testInfo) throws Throwable {
219 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
220 @Override
221 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
222 testGatheringWriteSameEventLoop(serverBootstrap, bootstrap);
223 }
224 });
225 }
226
227 private void testGatheringWriteSameEventLoop(ServerBootstrap sb, Bootstrap cb) throws Throwable {
228
229 try {
230 cb = cb.clone(cb.group().next());
231 } catch (UnsupportedOperationException e) {
232 throw new TestAbortedException("Not supported by this EventLoopGroup: " + cb.group(), e);
233 }
234
235 AtomicInteger sHandlersIdx = new AtomicInteger(0);
236 AtomicInteger cHandlersIdx = new AtomicInteger(0);
237 final TestServerHandler[] sHandlers = new TestServerHandler[] {
238 new TestServerHandler(true, ImmediateEventExecutor.INSTANCE.newPromise(), data.length),
239 new TestServerHandler(true, ImmediateEventExecutor.INSTANCE.newPromise(), data.length)
240 };
241 final TestHandler[] cHandlers = new TestHandler[] {
242 new TestHandler(true),
243 new TestHandler(true)
244 };
245
246 cb.handler(new ChannelInitializer<Channel>() {
247 @Override
248 protected void initChannel(Channel ch) throws Exception {
249 ch.pipeline().addLast(cHandlers[cHandlersIdx.getAndIncrement()]);
250 }
251 });
252
253 sb.childHandler(new ChannelInitializer<Channel>() {
254 @Override
255 protected void initChannel(Channel ch) throws Exception {
256 ch.pipeline().addLast(sHandlers[sHandlersIdx.getAndIncrement()]);
257 }
258 });
259
260 Channel sc = sb.bind().sync().channel();
261 Channel cc1 = cb.connect(sc.localAddress()).sync().channel();
262 Channel cc2 = cb.connect(sc.localAddress()).sync().channel();
263
264 assertSame(cc1.eventLoop(), cc2.eventLoop());
265 ChannelPromise p1 = cc1.newPromise();
266 ChannelPromise p2 = cc2.newPromise();
267 cc1.eventLoop().execute(() -> {
268 SplittableRandom rng = new SplittableRandom(random.nextLong());
269 for (int i = 0; i < data.length;) {
270 int length = Math.min(rng.nextInt(1024 * 8), data.length - i);
271 cc1.write(randomBufferType(cc1.alloc(), data, i, length));
272 cc2.write(randomBufferType(cc2.alloc(), data, i, length));
273 i += length;
274 }
275 cc1.writeAndFlush(Unpooled.EMPTY_BUFFER, p1);
276 cc2.writeAndFlush(Unpooled.EMPTY_BUFFER, p2);
277 });
278
279 assertTrue(p1.await(60000));
280 p1.sync();
281 assertTrue(p2.await(60000));
282 p2.sync();
283
284 for (int i = 0; i < sHandlers.length; i++) {
285 TestServerHandler sh = sHandlers[i];
286 TestHandler ch = cHandlers[i];
287 sh.doneReadingPromise.sync();
288 sh.channel.close().sync();
289 ch.channel.close().sync();
290
291 if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) {
292 throw sh.exception.get();
293 }
294 if (sh.exception.get() != null) {
295 throw sh.exception.get();
296 }
297 if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) {
298 throw ch.exception.get();
299 }
300 if (ch.exception.get() != null) {
301 throw ch.exception.get();
302 }
303 ByteBuf expected = wrappedBuffer(data);
304 assertEquals(expected, sh.received);
305 expected.release();
306 sh.received.release();
307 }
308 sc.close().sync();
309 }
310
311 private static final class TestServerHandler extends TestHandler {
312 private final int expectedBytes;
313 final Promise<Void> doneReadingPromise;
314 final ByteBuf received = Unpooled.buffer();
315
316 TestServerHandler(boolean autoRead, Promise<Void> doneReadingPromise, int expectedBytes) {
317 super(autoRead);
318 this.doneReadingPromise = doneReadingPromise;
319 this.expectedBytes = expectedBytes;
320 }
321
322 @Override
323 public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
324 received.writeBytes(in);
325 if (received.readableBytes() >= expectedBytes) {
326 doneReadingPromise.setSuccess(null);
327 }
328 }
329
330 @Override
331 void handleException(ChannelHandlerContext ctx, Throwable cause) {
332 doneReadingPromise.tryFailure(cause);
333 super.handleException(ctx, cause);
334 }
335
336 @Override
337 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
338 doneReadingPromise.tryFailure(new IllegalStateException("server closed!"));
339 super.channelInactive(ctx);
340 }
341 }
342
343 private static class TestHandler extends SimpleChannelInboundHandler<ByteBuf> {
344 private final boolean autoRead;
345 volatile Channel channel;
346 final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
347
348 TestHandler(boolean autoRead) {
349 this.autoRead = autoRead;
350 }
351
352 @Override
353 public final void channelActive(ChannelHandlerContext ctx) throws Exception {
354 channel = ctx.channel();
355 if (!autoRead) {
356 ctx.read();
357 }
358 super.channelActive(ctx);
359 }
360
361 @Override
362 public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
363 }
364
365 @Override
366 public final void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
367 if (!autoRead) {
368 ctx.read();
369 }
370 super.channelReadComplete(ctx);
371 }
372
373 @Override
374 public final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
375 if (exception.compareAndSet(null, cause)) {
376 handleException(ctx, cause);
377 }
378 super.exceptionCaught(ctx, cause);
379 }
380
381 void handleException(ChannelHandlerContext ctx, Throwable cause) {
382 ctx.close();
383 }
384 }
385 }