/Aurora/ClientStack/TokenBucket.cs
C# | 250 lines | 94 code | 24 blank | 132 comment | 11 complexity | 47af40ebdb290becee39ebc6a8e6b543 MD5 | raw file
1/* 2 * Copyright (c) Contributors, http://aurora-sim.org/, http://opensimulator.org/ 3 * See CONTRIBUTORS.TXT for a full list of copyright holders. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * * Neither the name of the Aurora-Sim Project nor the 13 * names of its contributors may be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY 17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY 20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28using System; 29 30namespace Aurora.ClientStack 31{ 32 /// <summary> 33 /// A hierarchical token bucket for bandwidth throttling. See 34 /// http://en.wikipedia.org/wiki/Token_bucket for more information 35 /// </summary> 36 public class TokenBucket 37 { 38 /// <summary> 39 /// Parent bucket to this bucket, or null if this is a root 40 /// bucket 41 /// </summary> 42 private readonly TokenBucket parent; 43 44 /// <summary> 45 /// Number of tokens currently in the bucket 46 /// </summary> 47 private int content; 48 49 /// <summary> 50 /// Time of the last drip, in system ticks 51 /// </summary> 52 private int lastDrip; 53 54 /// <summary> 55 /// Size of the bucket in bytes. If zero, the bucket has 56 /// infinite capacity 57 /// </summary> 58 private int maxBurst; 59 60 /// <summary> 61 /// Rate that the bucket fills, in bytes per millisecond. If 62 /// zero, the bucket always remains full 63 /// </summary> 64 private float tokensPerMS; 65 66 #region Properties 67 68 /// <summary> 69 /// The parent bucket of this bucket, or null if this bucket has no 70 /// parent. The parent bucket will limit the aggregate bandwidth of all 71 /// of its children buckets 72 /// </summary> 73 public TokenBucket Parent 74 { 75 get { return parent; } 76 } 77 78 /// <summary> 79 /// Maximum burst rate in bytes per second. This is the maximum number 80 /// of tokens that can accumulate in the bucket at any one time 81 /// </summary> 82 public int MaxBurst 83 { 84 get { return maxBurst; } 85 set { maxBurst = (value >= 0 ? value : 0); } 86 } 87 88 /// <summary> 89 /// The speed limit of this bucket in bytes per second. This is the 90 /// number of tokens that are added to the bucket per second 91 /// </summary> 92 /// <remarks> 93 /// Tokens are added to the bucket any time 94 /// <seealso> 95 /// <cref>RemoveTokens</cref> 96 /// </seealso> 97 /// is called, at the granularity of 98 /// the system tick interval (typically around 15-22ms) 99 /// </remarks> 100 public float DripRate 101 { 102 get { return tokensPerMS*1000; } 103 set 104 { 105 if (value == 0) 106 tokensPerMS = 0; 107 else 108 { 109 float bpms = (int) (value/1000.0f); 110 111 tokensPerMS = bpms <= 0.5f ? .5f : bpms; 112 } 113 } 114 } 115 116 /// <summary> 117 /// The speed limit of this bucket in bytes per millisecond 118 /// </summary> 119 public float DripPerMS 120 { 121 get { return tokensPerMS; } 122 } 123 124 /// <summary> 125 /// The number of bytes that can be sent at this moment. This is the 126 /// current number of tokens in the bucket 127 /// <remarks> 128 /// If this bucket has a parent bucket that does not have 129 /// enough tokens for a request, 130 /// <seealso> 131 /// <cref>RemoveTokens</cref> 132 /// </seealso> 133 /// will 134 /// return false regardless of the content of this bucket 135 /// </remarks> 136 /// </summary> 137 public int Content 138 { 139 get { return content; } 140 } 141 142 #endregion Properties 143 144 /// <summary> 145 /// Default constructor 146 /// </summary> 147 /// <param name="parent"> 148 /// Parent bucket if this is a child bucket, or 149 /// null if this is a root bucket 150 /// </param> 151 /// <param name="maxBurst"> 152 /// Maximum size of the bucket in bytes, or 153 /// zero if this bucket has no maximum capacity 154 /// </param> 155 /// <param name="dripRate"> 156 /// Rate that the bucket fills, in bytes per 157 /// second. If zero, the bucket always remains full 158 /// </param> 159 public TokenBucket(TokenBucket parent, int maxBurst, int dripRate) 160 { 161 this.parent = parent; 162 MaxBurst = maxBurst; 163 DripRate = dripRate; 164 lastDrip = Environment.TickCount & Int32.MaxValue; 165 } 166 167 /// <summary> 168 /// Remove a given number of tokens from the bucket 169 /// </summary> 170 /// <param name="amount">Number of tokens to remove from the bucket</param> 171 /// <returns> 172 /// True if the requested number of tokens were removed from 173 /// the bucket, otherwise false 174 /// </returns> 175 public bool RemoveTokens(int amount) 176 { 177 bool dummy; 178 return RemoveTokens(amount, out dummy); 179 } 180 181 /// <summary> 182 /// Remove a given number of tokens from the bucket 183 /// </summary> 184 /// <param name="amount">Number of tokens to remove from the bucket</param> 185 /// <param name="dripSucceeded"> 186 /// True if tokens were added to the bucket 187 /// during this call, otherwise false 188 /// </param> 189 /// <returns> 190 /// True if the requested number of tokens were removed from 191 /// the bucket, otherwise false 192 /// </returns> 193 public bool RemoveTokens(int amount, out bool dripSucceeded) 194 { 195 if (maxBurst == 0) 196 { 197 dripSucceeded = true; 198 return true; 199 } 200 201 dripSucceeded = Drip(); 202 203 if (content - amount >= 0) 204 { 205 if (parent != null && !parent.RemoveTokens(amount)) 206 return false; 207 208 content -= amount; 209 return true; 210 } 211 return false; 212 } 213 214 /// <summary> 215 /// Add tokens to the bucket over time. The number of tokens added each 216 /// call depends on the length of time that has passed since the last 217 /// call to Drip 218 /// </summary> 219 /// <returns>True if tokens were added to the bucket, otherwise false</returns> 220 public bool Drip() 221 { 222 if (tokensPerMS <= 0) 223 { 224 content = maxBurst; 225 return true; 226 } 227 int now = Environment.TickCount & Int32.MaxValue; 228 int deltaMS = now - lastDrip; 229 230 if (deltaMS <= 0) 231 { 232 if (deltaMS < 0) 233 lastDrip = now; 234 return false; 235 } 236 237 int dripAmount = (int) (deltaMS*tokensPerMS); 238 239 content = Math.Min(content + dripAmount, maxBurst); 240 lastDrip = now; 241/* 242 if (dripAmount < 0 || content < 0) 243 // sim has been idle for too long, integer has overflown 244 // previous calculation is meaningless, let's put it at correct max 245 content = maxBurst; 246*/ 247 return true; 248 } 249 } 250}