View Javadoc
1   /*
2    * Copyright 2013 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
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     // Test for https://github.com/netty/netty/issues/2647
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             // TODO: Remove this once we fix this test.
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         // Ensure all clients are on the same EventLoop.
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 }