1 /*
2 * Copyright 2012 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.netty5.handler.traffic;
17
18 import io.netty5.channel.ChannelHandlerContext;
19 import io.netty5.util.Resource;
20 import io.netty5.util.concurrent.Promise;
21
22 import java.util.ArrayDeque;
23 import java.util.concurrent.TimeUnit;
24
25 /**
26 * <p>This implementation of the {@link AbstractTrafficShapingHandler} is for channel
27 * traffic shaping, that is to say a per channel limitation of the bandwidth.</p>
28 * <p>Note the index used in {@code OutboundBuffer.setUserDefinedWritability(index, boolean)} is <b>1</b>.</p>
29 *
30 * <p>The general use should be as follow:</p>
31 * <ul>
32 * <li><p>Add in your pipeline a new ChannelTrafficShapingHandler.</p>
33 * <p><tt>ChannelTrafficShapingHandler myHandler = new ChannelTrafficShapingHandler();</tt></p>
34 * <p><tt>pipeline.addLast(myHandler);</tt></p>
35 *
36 * <p><b>Note that this handler has a Pipeline Coverage of "one" which means a new handler must be created
37 * for each new channel as the counter cannot be shared among all channels.</b>.</p>
38 *
39 * <p>Other arguments can be passed like write or read limitation (in bytes/s where 0 means no limitation)
40 * or the check interval (in millisecond) that represents the delay between two computations of the
41 * bandwidth and so the call back of the doAccounting method (0 means no accounting at all).</p>
42 *
43 * <p>A value of 0 means no accounting for checkInterval. If you need traffic shaping but no such accounting,
44 * it is recommended to set a positive value, even if it is high since the precision of the
45 * Traffic Shaping depends on the period where the traffic is computed. The highest the interval,
46 * the less precise the traffic shaping will be. It is suggested as higher value something close
47 * to 5 or 10 minutes.</p>
48 *
49 * <p>maxTimeToWait, by default set to 15s, allows to specify an upper bound of time shaping.</p>
50 * </li>
51 * <li>In your handler, you should consider to use the {@code channel.isWritable()} and
52 * {@code channelWritabilityChanged(ctx)} to handle writability, or through
53 * {@code future.addListener(future -> ...)} on the future returned by
54 * {@code ctx.write()}.</li>
55 * <li><p>You shall also consider to have object size in read or write operations relatively adapted to
56 * the bandwidth you required: for instance having 10 MB objects for 10KB/s will lead to burst effect,
57 * while having 100 KB objects for 1 MB/s should be smoothly handle by this TrafficShaping handler.</p></li>
58 * <li><p>Some configuration methods will be taken as best effort, meaning
59 * that all already scheduled traffics will not be
60 * changed, but only applied to new traffics.</p>
61 * <p>So the expected usage of those methods are to be used not too often,
62 * accordingly to the traffic shaping configuration.</p></li>
63 * </ul>
64 */
65 public class ChannelTrafficShapingHandler extends AbstractTrafficShapingHandler {
66 private final ArrayDeque<ToSend> messagesQueue = new ArrayDeque<>();
67 private long queueSize;
68
69 /**
70 * Create a new instance.
71 *
72 * @param writeLimit
73 * 0 or a limit in bytes/s
74 * @param readLimit
75 * 0 or a limit in bytes/s
76 * @param checkInterval
77 * The delay between two computations of performances for
78 * channels or 0 if no stats are to be computed.
79 * @param maxTime
80 * The maximum delay to wait in case of traffic excess.
81 */
82 public ChannelTrafficShapingHandler(long writeLimit, long readLimit,
83 long checkInterval, long maxTime) {
84 super(writeLimit, readLimit, checkInterval, maxTime);
85 }
86
87 /**
88 * Create a new instance using default
89 * max time as delay allowed value of 15000 ms.
90 *
91 * @param writeLimit
92 * 0 or a limit in bytes/s
93 * @param readLimit
94 * 0 or a limit in bytes/s
95 * @param checkInterval
96 * The delay between two computations of performances for
97 * channels or 0 if no stats are to be computed.
98 */
99 public ChannelTrafficShapingHandler(long writeLimit,
100 long readLimit, long checkInterval) {
101 super(writeLimit, readLimit, checkInterval);
102 }
103
104 /**
105 * Create a new instance using default Check Interval value of 1000 ms and
106 * max time as delay allowed value of 15000 ms.
107 *
108 * @param writeLimit
109 * 0 or a limit in bytes/s
110 * @param readLimit
111 * 0 or a limit in bytes/s
112 */
113 public ChannelTrafficShapingHandler(long writeLimit,
114 long readLimit) {
115 super(writeLimit, readLimit);
116 }
117
118 /**
119 * Create a new instance using
120 * default max time as delay allowed value of 15000 ms and no limit.
121 *
122 * @param checkInterval
123 * The delay between two computations of performances for
124 * channels or 0 if no stats are to be computed.
125 */
126 public ChannelTrafficShapingHandler(long checkInterval) {
127 super(checkInterval);
128 }
129
130 @Override
131 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
132 TrafficCounter trafficCounter = new TrafficCounter(this, ctx.executor(), "ChannelTC" +
133 ctx.channel().hashCode(), checkInterval);
134 setTrafficCounter(trafficCounter);
135 trafficCounter.start();
136 super.handlerAdded(ctx);
137 }
138
139 @Override
140 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
141 trafficCounter.stop();
142 // write order control
143 synchronized (this) {
144 if (ctx.channel().isActive()) {
145 for (ToSend toSend : messagesQueue) {
146 long size = calculateSize(toSend.toSend);
147 trafficCounter.bytesRealWriteFlowControl(size);
148 queueSize -= size;
149 ctx.write(toSend.toSend).cascadeTo(toSend.promise);
150 }
151 } else {
152 for (ToSend toSend : messagesQueue) {
153 if (Resource.isAccessible(toSend.toSend, false)) {
154 Resource.dispose(toSend.toSend);
155 }
156 }
157 }
158 messagesQueue.clear();
159 }
160 releaseWriteSuspended(ctx);
161 releaseReadSuspended(ctx);
162 super.handlerRemoved(ctx);
163 }
164
165 private static final class ToSend {
166 final long relativeTimeAction;
167 final Object toSend;
168 final Promise<Void> promise;
169
170 private ToSend(final long delay, final Object toSend, final Promise<Void> promise) {
171 relativeTimeAction = delay;
172 this.toSend = toSend;
173 this.promise = promise;
174 }
175 }
176
177 @Override
178 void submitWrite(final ChannelHandlerContext ctx, final Object msg,
179 final long size, final long delay, final long now,
180 final Promise<Void> promise) {
181 final ToSend newToSend;
182 // write order control
183 synchronized (this) {
184 if (delay == 0 && messagesQueue.isEmpty()) {
185 trafficCounter.bytesRealWriteFlowControl(size);
186 ctx.write(msg).cascadeTo(promise);
187 return;
188 }
189 newToSend = new ToSend(delay + now, msg, promise);
190 messagesQueue.addLast(newToSend);
191 queueSize += size;
192 checkWriteSuspend(ctx, delay, queueSize);
193 }
194 final long futureNow = newToSend.relativeTimeAction;
195 ctx.executor().schedule(() -> sendAllValid(ctx, futureNow), delay, TimeUnit.MILLISECONDS);
196 }
197
198 private void sendAllValid(final ChannelHandlerContext ctx, final long now) {
199 // write order control
200 synchronized (this) {
201 ToSend newToSend = messagesQueue.pollFirst();
202 for (; newToSend != null; newToSend = messagesQueue.pollFirst()) {
203 if (newToSend.relativeTimeAction <= now) {
204 long size = calculateSize(newToSend.toSend);
205 trafficCounter.bytesRealWriteFlowControl(size);
206 queueSize -= size;
207 ctx.write(newToSend.toSend).cascadeTo(newToSend.promise);
208 } else {
209 messagesQueue.addFirst(newToSend);
210 break;
211 }
212 }
213 if (messagesQueue.isEmpty()) {
214 releaseWriteSuspended(ctx);
215 }
216 }
217 ctx.flush();
218 }
219
220 /**
221 * @return current size in bytes of the write buffer.
222 */
223 public long queueSize() {
224 return queueSize;
225 }
226 }