View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    * The Netty Project licenses this file to you under the Apache License,
4    * version 2.0 (the "License"); you may not use this file except in compliance
5    * with the License. You may obtain a copy of the License at:
6    * https://www.apache.org/licenses/LICENSE-2.0
7    * Unless required by applicable law or agreed to in writing, software
8    * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9    * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10   * License for the specific language governing permissions and limitations
11   * under the License.
12   */
13  package io.netty.testsuite.transport.socket;
14  
15  import io.netty.bootstrap.Bootstrap;
16  import io.netty.bootstrap.ServerBootstrap;
17  import io.netty.buffer.ByteBuf;
18  import io.netty.buffer.Unpooled;
19  import io.netty.channel.Channel;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.channel.ChannelInitializer;
22  import io.netty.channel.SimpleChannelInboundHandler;
23  import io.netty.channel.socket.SocketChannel;
24  import io.netty.handler.traffic.AbstractTrafficShapingHandler;
25  import io.netty.handler.traffic.ChannelTrafficShapingHandler;
26  import io.netty.handler.traffic.GlobalTrafficShapingHandler;
27  import io.netty.handler.traffic.TrafficCounter;
28  import io.netty.util.concurrent.DefaultEventExecutorGroup;
29  import io.netty.util.concurrent.EventExecutorGroup;
30  import io.netty.util.concurrent.Promise;
31  import io.netty.util.internal.logging.InternalLogger;
32  import io.netty.util.internal.logging.InternalLoggerFactory;
33  import org.junit.jupiter.api.AfterAll;
34  import org.junit.jupiter.api.BeforeAll;
35  import org.junit.jupiter.api.Test;
36  import org.junit.jupiter.api.TestInfo;
37  import org.junit.jupiter.api.Timeout;
38  
39  import java.io.IOException;
40  import java.util.Arrays;
41  import java.util.concurrent.Executors;
42  import java.util.concurrent.ScheduledExecutorService;
43  import java.util.concurrent.ThreadLocalRandom;
44  import java.util.concurrent.TimeUnit;
45  import java.util.concurrent.atomic.AtomicReference;
46  
47  import static org.junit.jupiter.api.Assertions.assertTrue;
48  
49  public class TrafficShapingHandlerTest extends AbstractSocketTest {
50      private static final InternalLogger logger = InternalLoggerFactory.getInstance(TrafficShapingHandlerTest.class);
51      private static final InternalLogger loggerServer = InternalLoggerFactory.getInstance("ServerTSH");
52      private static final InternalLogger loggerClient = InternalLoggerFactory.getInstance("ClientTSH");
53  
54      static final int messageSize = 1024;
55      static final int bandwidthFactor = 12;
56      static final int minfactor = 3;
57      static final int maxfactor = bandwidthFactor + bandwidthFactor / 2;
58      static final long stepms = (1000 / bandwidthFactor - 10) / 10 * 10;
59      static final long minimalms = Math.max(stepms / 2, 20) / 10 * 10;
60      static final long check = 10;
61      static final byte[] data = new byte[messageSize];
62  
63      private static final String TRAFFIC = "traffic";
64      private static String currentTestName;
65      private static int currentTestRun;
66  
67      private static EventExecutorGroup group;
68      private static EventExecutorGroup groupForGlobal;
69      private static final ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
70      static {
71          ThreadLocalRandom.current().nextBytes(data);
72      }
73  
74      @BeforeAll
75      public static void createGroup() {
76          logger.info("Bandwidth: " + minfactor + " <= " + bandwidthFactor + " <= " + maxfactor +
77                      " StepMs: " + stepms + " MinMs: " + minimalms + " CheckMs: " + check);
78          group = new DefaultEventExecutorGroup(8);
79          groupForGlobal = new DefaultEventExecutorGroup(8);
80      }
81  
82      @AfterAll
83      public static void destroyGroup() throws Exception {
84          group.shutdownGracefully().sync();
85          groupForGlobal.shutdownGracefully().sync();
86          executor.shutdown();
87      }
88  
89      private static long[] computeWaitRead(int[] multipleMessage) {
90          long[] minimalWaitBetween = new long[multipleMessage.length + 1];
91          minimalWaitBetween[0] = 0;
92          for (int i = 0; i < multipleMessage.length; i++) {
93              if (multipleMessage[i] > 1) {
94                  minimalWaitBetween[i + 1] = (multipleMessage[i] - 1) * stepms + minimalms;
95              } else {
96                  minimalWaitBetween[i + 1] = 10;
97              }
98          }
99          return minimalWaitBetween;
100     }
101 
102     private static long[] computeWaitWrite(int[] multipleMessage) {
103         long[] minimalWaitBetween = new long[multipleMessage.length + 1];
104         for (int i = 0; i < multipleMessage.length; i++) {
105             if (multipleMessage[i] > 1) {
106                 minimalWaitBetween[i] = (multipleMessage[i] - 1) * stepms + minimalms;
107             } else {
108                 minimalWaitBetween[i] = 10;
109             }
110         }
111         return minimalWaitBetween;
112     }
113 
114     private static long[] computeWaitAutoRead(int []autoRead) {
115         long [] minimalWaitBetween = new long[autoRead.length + 1];
116         minimalWaitBetween[0] = 0;
117         for (int i = 0; i < autoRead.length; i++) {
118             if (autoRead[i] != 0) {
119                 if (autoRead[i] > 0) {
120                     minimalWaitBetween[i + 1] = -1;
121                 } else {
122                     minimalWaitBetween[i + 1] = check;
123                 }
124             } else {
125                 minimalWaitBetween[i + 1] = 0;
126             }
127         }
128         return minimalWaitBetween;
129     }
130 
131     @Test
132     @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
133     public void testNoTrafficShapping(TestInfo testInfo) throws Throwable {
134         currentTestName = "TEST NO TRAFFIC";
135         currentTestRun = 0;
136         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
137             @Override
138             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
139                 testNoTrafficShapping(serverBootstrap, bootstrap);
140             }
141         });
142     }
143 
144     public void testNoTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
145         int[] autoRead = null;
146         int[] multipleMessage = { 1, 2, 1 };
147         long[] minimalWaitBetween = null;
148         testTrafficShapping0(sb, cb, false, false, false, false, autoRead, minimalWaitBetween, multipleMessage);
149     }
150 
151     @Test
152     @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
153     public void testWriteTrafficShapping(TestInfo testInfo) throws Throwable {
154         currentTestName = "TEST WRITE";
155         currentTestRun = 0;
156         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
157             @Override
158             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
159                 testWriteTrafficShapping(serverBootstrap, bootstrap);
160             }
161         });
162     }
163 
164     public void testWriteTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
165         int[] autoRead = null;
166         int[] multipleMessage = { 1, 2, 1, 1 };
167         long[] minimalWaitBetween = computeWaitWrite(multipleMessage);
168         testTrafficShapping0(sb, cb, false, false, true, false, autoRead, minimalWaitBetween, multipleMessage);
169     }
170 
171     @Test
172     @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
173     public void testReadTrafficShapping(TestInfo testInfo) throws Throwable {
174         currentTestName = "TEST READ";
175         currentTestRun = 0;
176         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
177             @Override
178             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
179                 testReadTrafficShapping(serverBootstrap, bootstrap);
180             }
181         });
182     }
183 
184     public void testReadTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
185         int[] autoRead = null;
186         int[] multipleMessage = { 1, 2, 1, 1 };
187         long[] minimalWaitBetween = computeWaitRead(multipleMessage);
188         testTrafficShapping0(sb, cb, false, true, false, false, autoRead, minimalWaitBetween, multipleMessage);
189     }
190 
191     @Test
192     @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
193     public void testWrite1TrafficShapping(TestInfo testInfo) throws Throwable {
194         currentTestName = "TEST WRITE";
195         currentTestRun = 0;
196         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
197             @Override
198             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
199                 testWrite1TrafficShapping(serverBootstrap, bootstrap);
200             }
201         });
202     }
203 
204     public void testWrite1TrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
205         int[] autoRead = null;
206         int[] multipleMessage = { 1, 1, 1 };
207         long[] minimalWaitBetween = computeWaitWrite(multipleMessage);
208         testTrafficShapping0(sb, cb, false, false, true, false, autoRead, minimalWaitBetween, multipleMessage);
209     }
210 
211     @Test
212     @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
213     public void testRead1TrafficShapping(TestInfo testInfo) throws Throwable {
214         currentTestName = "TEST READ";
215         currentTestRun = 0;
216         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
217             @Override
218             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
219                 testRead1TrafficShapping(serverBootstrap, bootstrap);
220             }
221         });
222     }
223 
224     public void testRead1TrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
225         int[] autoRead = null;
226         int[] multipleMessage = { 1, 1, 1 };
227         long[] minimalWaitBetween = computeWaitRead(multipleMessage);
228         testTrafficShapping0(sb, cb, false, true, false, false, autoRead, minimalWaitBetween, multipleMessage);
229     }
230 
231     @Test
232     @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
233     public void testWriteGlobalTrafficShapping(TestInfo testInfo) throws Throwable {
234         currentTestName = "TEST GLOBAL WRITE";
235         currentTestRun = 0;
236         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
237             @Override
238             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
239                 testWriteGlobalTrafficShapping(serverBootstrap, bootstrap);
240             }
241         });
242     }
243 
244     public void testWriteGlobalTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
245         int[] autoRead = null;
246         int[] multipleMessage = { 1, 2, 1, 1 };
247         long[] minimalWaitBetween = computeWaitWrite(multipleMessage);
248         testTrafficShapping0(sb, cb, false, false, true, true, autoRead, minimalWaitBetween, multipleMessage);
249     }
250 
251     @Test
252     @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
253     public void testReadGlobalTrafficShapping(TestInfo testInfo) throws Throwable {
254         currentTestName = "TEST GLOBAL READ";
255         currentTestRun = 0;
256         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
257             @Override
258             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
259                 testReadGlobalTrafficShapping(serverBootstrap, bootstrap);
260             }
261         });
262     }
263 
264     public void testReadGlobalTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
265         int[] autoRead = null;
266         int[] multipleMessage = { 1, 2, 1, 1 };
267         long[] minimalWaitBetween = computeWaitRead(multipleMessage);
268         testTrafficShapping0(sb, cb, false, true, false, true, autoRead, minimalWaitBetween, multipleMessage);
269     }
270 
271     @Test
272     @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
273     public void testAutoReadTrafficShapping(TestInfo testInfo) throws Throwable {
274         currentTestName = "TEST AUTO READ";
275         currentTestRun = 0;
276         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
277             @Override
278             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
279                 testAutoReadTrafficShapping(serverBootstrap, bootstrap);
280             }
281         });
282     }
283 
284     public void testAutoReadTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
285         int[] autoRead = { 1, -1, -1, 1, -2, 0, 1, 0, -3, 0, 1, 2, 0 };
286         int[] multipleMessage = new int[autoRead.length];
287         Arrays.fill(multipleMessage, 1);
288         long[] minimalWaitBetween = computeWaitAutoRead(autoRead);
289         testTrafficShapping0(sb, cb, false, true, false, false, autoRead, minimalWaitBetween, multipleMessage);
290     }
291 
292     @Test
293     @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
294     public void testAutoReadGlobalTrafficShapping(TestInfo testInfo) throws Throwable {
295         currentTestName = "TEST AUTO READ GLOBAL";
296         currentTestRun = 0;
297         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
298             @Override
299             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
300                 testAutoReadGlobalTrafficShapping(serverBootstrap, bootstrap);
301             }
302         });
303     }
304 
305     public void testAutoReadGlobalTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
306         int[] autoRead = { 1, -1, -1, 1, -2, 0, 1, 0, -3, 0, 1, 2, 0 };
307         int[] multipleMessage = new int[autoRead.length];
308         Arrays.fill(multipleMessage, 1);
309         long[] minimalWaitBetween = computeWaitAutoRead(autoRead);
310         testTrafficShapping0(sb, cb, false, true, false, true, autoRead, minimalWaitBetween, multipleMessage);
311     }
312 
313     /**
314      *
315      * @param additionalExecutor
316      *            shall the pipeline add the handler using an additional executor
317      * @param limitRead
318      *            True to set Read Limit on Server side
319      * @param limitWrite
320      *            True to set Write Limit on Client side
321      * @param globalLimit
322      *            True to change Channel to Global TrafficShapping
323      * @param minimalWaitBetween
324      *            time in ms that should be waited before getting the final result (note: for READ the values are
325      *            right shifted once, the first value being 0)
326      * @param multipleMessage
327      *            how many message to send at each step (for READ: the first should be 1, as the two last steps to
328      *            ensure correct testing)
329      * @throws Throwable
330      */
331     private static void testTrafficShapping0(
332             ServerBootstrap sb, Bootstrap cb, final boolean additionalExecutor,
333             final boolean limitRead, final boolean limitWrite, final boolean globalLimit, int[] autoRead,
334             long[] minimalWaitBetween, int[] multipleMessage) throws Throwable {
335 
336         currentTestRun++;
337         logger.info("TEST: " + currentTestName + " RUN: " + currentTestRun +
338                     " Exec: " + additionalExecutor + " Read: " + limitRead + " Write: " + limitWrite + " Global: "
339                     + globalLimit);
340         final ServerHandler sh = new ServerHandler(autoRead, multipleMessage);
341         Promise<Boolean> promise = group.next().newPromise();
342         final ClientHandler ch = new ClientHandler(promise, minimalWaitBetween, multipleMessage,
343                                                    autoRead);
344 
345         final AbstractTrafficShapingHandler handler;
346         if (limitRead) {
347             if (globalLimit) {
348                 handler = new GlobalTrafficShapingHandler(groupForGlobal, 0, bandwidthFactor * messageSize, check);
349             } else {
350                 handler = new ChannelTrafficShapingHandler(0, bandwidthFactor * messageSize, check);
351             }
352         } else if (limitWrite) {
353             if (globalLimit) {
354                 handler = new GlobalTrafficShapingHandler(groupForGlobal, bandwidthFactor * messageSize, 0, check);
355             } else {
356                 handler = new ChannelTrafficShapingHandler(bandwidthFactor * messageSize, 0, check);
357             }
358         } else {
359             handler = null;
360         }
361 
362         sb.childHandler(new ChannelInitializer<SocketChannel>() {
363             @Override
364             protected void initChannel(SocketChannel c) throws Exception {
365                 if (limitRead) {
366                     c.pipeline().addLast(TRAFFIC, handler);
367                 }
368                 c.pipeline().addLast(sh);
369             }
370         });
371         cb.handler(new ChannelInitializer<SocketChannel>() {
372             @Override
373             protected void initChannel(SocketChannel c) throws Exception {
374                 if (limitWrite) {
375                     c.pipeline().addLast(TRAFFIC, handler);
376                 }
377                 c.pipeline().addLast(ch);
378             }
379         });
380 
381         Channel sc = sb.bind().sync().channel();
382         Channel cc = cb.connect(sc.localAddress()).sync().channel();
383 
384         int totalNb = 0;
385         for (int i = 1; i < multipleMessage.length; i++) {
386             totalNb += multipleMessage[i];
387         }
388         Long start = TrafficCounter.milliSecondFromNano();
389         int nb = multipleMessage[0];
390         for (int i = 0; i < nb; i++) {
391             cc.write(cc.alloc().buffer().writeBytes(data));
392         }
393         cc.flush();
394 
395         promise.await();
396         Long stop = TrafficCounter.milliSecondFromNano();
397         assertTrue(promise.isSuccess(), "Error during execution of TrafficShapping: " + promise.cause());
398 
399         float average = (totalNb * messageSize) / (float) (stop - start);
400         logger.info("TEST: " + currentTestName + " RUN: " + currentTestRun +
401                     " Average of traffic: " + average + " compare to " + bandwidthFactor);
402         sh.channel.close().sync();
403         ch.channel.close().sync();
404         sc.close().sync();
405         if (autoRead != null) {
406             // for extra release call in AutoRead
407             Thread.sleep(minimalms);
408         }
409 
410         if (autoRead == null && minimalWaitBetween != null) {
411             assertTrue(average <= maxfactor,
412                 "Overall Traffic not ok since > " + maxfactor + ": " + average);
413             if (additionalExecutor) {
414                 // Oio is not as good when using additionalExecutor
415                 assertTrue(average >= 0.25, "Overall Traffic not ok since < 0.25: " + average);
416             } else {
417                 assertTrue(average >= minfactor,
418                     "Overall Traffic not ok since < " + minfactor + ": " + average);
419             }
420         }
421         if (handler != null && globalLimit) {
422             ((GlobalTrafficShapingHandler) handler).release();
423         }
424 
425         if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) {
426             throw sh.exception.get();
427         }
428         if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) {
429             throw ch.exception.get();
430         }
431         if (sh.exception.get() != null) {
432             throw sh.exception.get();
433         }
434         if (ch.exception.get() != null) {
435             throw ch.exception.get();
436         }
437     }
438 
439     private static class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
440         volatile Channel channel;
441         final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
442         volatile int step;
443         // first message will always be validated
444         private long currentLastTime = TrafficCounter.milliSecondFromNano();
445         private final long[] minimalWaitBetween;
446         private final int[] multipleMessage;
447         private final int[] autoRead;
448         final Promise<Boolean> promise;
449 
450         ClientHandler(Promise<Boolean> promise, long[] minimalWaitBetween, int[] multipleMessage,
451                       int[] autoRead) {
452             this.minimalWaitBetween = minimalWaitBetween;
453             this.multipleMessage = Arrays.copyOf(multipleMessage, multipleMessage.length);
454             this.promise = promise;
455             this.autoRead = autoRead;
456         }
457 
458         @Override
459         public void channelActive(ChannelHandlerContext ctx) throws Exception {
460             channel = ctx.channel();
461         }
462 
463         @Override
464         public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
465             long lastTimestamp = 0;
466             loggerClient.debug("Step: " + step + " Read: " + in.readableBytes() / 8 + " blocks");
467             while (in.isReadable()) {
468                 lastTimestamp = in.readLong();
469                 multipleMessage[step]--;
470             }
471             if (multipleMessage[step] > 0) {
472                 // still some message to get
473                 return;
474             }
475             long minimalWait = minimalWaitBetween != null? minimalWaitBetween[step] : 0;
476             int ar = 0;
477             if (autoRead != null) {
478                 if (step > 0 && autoRead[step - 1] != 0) {
479                     ar = autoRead[step - 1];
480                 }
481             }
482             loggerClient.info("Step: " + step + " Interval: " + (lastTimestamp - currentLastTime) + " compareTo "
483                               + minimalWait + " (" + ar + ')');
484             assertTrue(lastTimestamp - currentLastTime >= minimalWait,
485                     "The interval of time is incorrect:" + (lastTimestamp - currentLastTime) + " not> " + minimalWait);
486             currentLastTime = lastTimestamp;
487             step++;
488             if (multipleMessage.length > step) {
489                 int nb = multipleMessage[step];
490                 for (int i = 0; i < nb; i++) {
491                     channel.write(channel.alloc().buffer().writeBytes(data));
492                 }
493                 channel.flush();
494             } else {
495                 promise.setSuccess(true);
496             }
497         }
498 
499         @Override
500         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
501             if (exception.compareAndSet(null, cause)) {
502                 cause.printStackTrace();
503                 promise.setFailure(cause);
504                 ctx.close();
505             }
506         }
507     }
508 
509     private static class ServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
510         private final int[] autoRead;
511         private final int[] multipleMessage;
512         volatile Channel channel;
513         volatile int step;
514         final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
515 
516         ServerHandler(int[] autoRead, int[] multipleMessage) {
517             this.autoRead = autoRead;
518             this.multipleMessage = Arrays.copyOf(multipleMessage, multipleMessage.length);
519         }
520 
521         @Override
522         public void channelActive(ChannelHandlerContext ctx) throws Exception {
523             channel = ctx.channel();
524         }
525 
526         @Override
527         public void channelRead0(final ChannelHandlerContext ctx, ByteBuf in) throws Exception {
528             byte[] actual = new byte[in.readableBytes()];
529             int nb = actual.length / messageSize;
530             loggerServer.info("Step: " + step + " Read: " + nb + " blocks");
531             in.readBytes(actual);
532             long timestamp = TrafficCounter.milliSecondFromNano();
533             int isAutoRead = 0;
534             int laststep = step;
535             for (int i = 0; i < nb; i++) {
536                 multipleMessage[step]--;
537                 if (multipleMessage[step] == 0) {
538                     // setAutoRead test
539                     if (autoRead != null) {
540                         isAutoRead = autoRead[step];
541                     }
542                     step++;
543                 }
544             }
545             if (laststep != step) {
546                 // setAutoRead test
547                 if (autoRead != null && isAutoRead != 2) {
548                     if (isAutoRead != 0) {
549                         loggerServer.info("Step: " + step + " Set AutoRead: " + (isAutoRead > 0));
550                         channel.config().setAutoRead(isAutoRead > 0);
551                     } else {
552                         loggerServer.info("Step: " + step + " AutoRead: NO");
553                     }
554                 }
555             }
556             Thread.sleep(10);
557             loggerServer.debug("Step: " + step + " Write: " + nb);
558             for (int i = 0; i < nb; i++) {
559                 channel.write(Unpooled.copyLong(timestamp));
560             }
561             channel.flush();
562             if (laststep != step) {
563                 // setAutoRead test
564                 if (isAutoRead != 0) {
565                     if (isAutoRead < 0) {
566                         final int exactStep = step;
567                         long wait = isAutoRead == -1? minimalms : stepms + minimalms;
568                         if (isAutoRead == -3) {
569                             wait = stepms * 3;
570                         }
571                         executor.schedule(new Runnable() {
572                             @Override
573                             public void run() {
574                                 loggerServer.info("Step: " + exactStep + " Reset AutoRead");
575                                 channel.config().setAutoRead(true);
576                             }
577                         }, wait, TimeUnit.MILLISECONDS);
578                     } else {
579                         if (isAutoRead > 1) {
580                             loggerServer.debug("Step: " + step + " Will Set AutoRead: True");
581                             final int exactStep = step;
582                             executor.schedule(new Runnable() {
583                                 @Override
584                                 public void run() {
585                                     loggerServer.info("Step: " + exactStep + " Set AutoRead: True");
586                                     channel.config().setAutoRead(true);
587                                 }
588                             }, stepms + minimalms, TimeUnit.MILLISECONDS);
589                         }
590                     }
591                 }
592             }
593         }
594 
595         @Override
596         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
597             if (exception.compareAndSet(null, cause)) {
598                 cause.printStackTrace();
599                 ctx.close();
600             }
601         }
602     }
603 }