diff options
Diffstat (limited to 'OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs')
-rw-r--r-- | OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs new file mode 100644 index 0000000..ca5501d --- /dev/null +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs | |||
@@ -0,0 +1,697 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, 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 OpenSimulator 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 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Net; | ||
31 | using System.Threading; | ||
32 | using log4net; | ||
33 | using OpenSim.Framework; | ||
34 | using OpenMetaverse; | ||
35 | using OpenMetaverse.Packets; | ||
36 | |||
37 | using TokenBucket = OpenSim.Region.ClientStack.LindenUDP.TokenBucket; | ||
38 | |||
39 | namespace OpenSim.Region.ClientStack.LindenUDP | ||
40 | { | ||
41 | #region Delegates | ||
42 | |||
43 | /// <summary> | ||
44 | /// Fired when updated networking stats are produced for this client | ||
45 | /// </summary> | ||
46 | /// <param name="inPackets">Number of incoming packets received since this | ||
47 | /// event was last fired</param> | ||
48 | /// <param name="outPackets">Number of outgoing packets sent since this | ||
49 | /// event was last fired</param> | ||
50 | /// <param name="unAckedBytes">Current total number of bytes in packets we | ||
51 | /// are waiting on ACKs for</param> | ||
52 | public delegate void PacketStats(int inPackets, int outPackets, int unAckedBytes); | ||
53 | /// <summary> | ||
54 | /// Fired when the queue for one or more packet categories is empty. This | ||
55 | /// event can be hooked to put more data on the empty queues | ||
56 | /// </summary> | ||
57 | /// <param name="category">Categories of the packet queues that are empty</param> | ||
58 | public delegate void QueueEmpty(ThrottleOutPacketTypeFlags categories); | ||
59 | |||
60 | #endregion Delegates | ||
61 | |||
62 | /// <summary> | ||
63 | /// Tracks state for a client UDP connection and provides client-specific methods | ||
64 | /// </summary> | ||
65 | public sealed class LLUDPClient | ||
66 | { | ||
67 | // TODO: Make this a config setting | ||
68 | /// <summary>Percentage of the task throttle category that is allocated to avatar and prim | ||
69 | /// state updates</summary> | ||
70 | const float STATE_TASK_PERCENTAGE = 0.8f; | ||
71 | |||
72 | private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); | ||
73 | |||
74 | /// <summary>The number of packet categories to throttle on. If a throttle category is added | ||
75 | /// or removed, this number must also change</summary> | ||
76 | const int THROTTLE_CATEGORY_COUNT = 8; | ||
77 | |||
78 | /// <summary>Fired when updated networking stats are produced for this client</summary> | ||
79 | public event PacketStats OnPacketStats; | ||
80 | /// <summary>Fired when the queue for a packet category is empty. This event can be | ||
81 | /// hooked to put more data on the empty queue</summary> | ||
82 | public event QueueEmpty OnQueueEmpty; | ||
83 | |||
84 | /// <summary>AgentID for this client</summary> | ||
85 | public readonly UUID AgentID; | ||
86 | /// <summary>The remote address of the connected client</summary> | ||
87 | public readonly IPEndPoint RemoteEndPoint; | ||
88 | /// <summary>Circuit code that this client is connected on</summary> | ||
89 | public readonly uint CircuitCode; | ||
90 | /// <summary>Sequence numbers of packets we've received (for duplicate checking)</summary> | ||
91 | public readonly IncomingPacketHistoryCollection PacketArchive = new IncomingPacketHistoryCollection(200); | ||
92 | /// <summary>Packets we have sent that need to be ACKed by the client</summary> | ||
93 | public readonly UnackedPacketCollection NeedAcks = new UnackedPacketCollection(); | ||
94 | /// <summary>ACKs that are queued up, waiting to be sent to the client</summary> | ||
95 | public readonly OpenSim.Framework.LocklessQueue<uint> PendingAcks = new OpenSim.Framework.LocklessQueue<uint>(); | ||
96 | |||
97 | /// <summary>Current packet sequence number</summary> | ||
98 | public int CurrentSequence; | ||
99 | /// <summary>Current ping sequence number</summary> | ||
100 | public byte CurrentPingSequence; | ||
101 | /// <summary>True when this connection is alive, otherwise false</summary> | ||
102 | public bool IsConnected = true; | ||
103 | /// <summary>True when this connection is paused, otherwise false</summary> | ||
104 | public bool IsPaused; | ||
105 | /// <summary>Environment.TickCount when the last packet was received for this client</summary> | ||
106 | public int TickLastPacketReceived; | ||
107 | |||
108 | /// <summary>Smoothed round-trip time. A smoothed average of the round-trip time for sending a | ||
109 | /// reliable packet to the client and receiving an ACK</summary> | ||
110 | public float SRTT; | ||
111 | /// <summary>Round-trip time variance. Measures the consistency of round-trip times</summary> | ||
112 | public float RTTVAR; | ||
113 | /// <summary>Retransmission timeout. Packets that have not been acknowledged in this number of | ||
114 | /// milliseconds or longer will be resent</summary> | ||
115 | /// <remarks>Calculated from <seealso cref="SRTT"/> and <seealso cref="RTTVAR"/> using the | ||
116 | /// guidelines in RFC 2988</remarks> | ||
117 | public int RTO; | ||
118 | /// <summary>Number of bytes received since the last acknowledgement was sent out. This is used | ||
119 | /// to loosely follow the TCP delayed ACK algorithm in RFC 1122 (4.2.3.2)</summary> | ||
120 | public int BytesSinceLastACK; | ||
121 | /// <summary>Number of packets received from this client</summary> | ||
122 | public int PacketsReceived; | ||
123 | /// <summary>Number of packets sent to this client</summary> | ||
124 | public int PacketsSent; | ||
125 | /// <summary>Number of packets resent to this client</summary> | ||
126 | public int PacketsResent; | ||
127 | /// <summary>Total byte count of unacked packets sent to this client</summary> | ||
128 | public int UnackedBytes; | ||
129 | |||
130 | /// <summary>Total number of received packets that we have reported to the OnPacketStats event(s)</summary> | ||
131 | private int m_packetsReceivedReported; | ||
132 | /// <summary>Total number of sent packets that we have reported to the OnPacketStats event(s)</summary> | ||
133 | private int m_packetsSentReported; | ||
134 | /// <summary>Holds the Environment.TickCount value of when the next OnQueueEmpty can be fired</summary> | ||
135 | private int m_nextOnQueueEmpty = 1; | ||
136 | |||
137 | /// <summary>Throttle bucket for this agent's connection</summary> | ||
138 | private readonly AdaptiveTokenBucket m_throttleClient; | ||
139 | public AdaptiveTokenBucket FlowThrottle | ||
140 | { | ||
141 | get { return m_throttleClient; } | ||
142 | } | ||
143 | |||
144 | /// <summary>Throttle bucket for this agent's connection</summary> | ||
145 | private readonly TokenBucket m_throttleCategory; | ||
146 | /// <summary>Throttle buckets for each packet category</summary> | ||
147 | private readonly TokenBucket[] m_throttleCategories; | ||
148 | /// <summary>Outgoing queues for throttled packets</summary> | ||
149 | private readonly OpenSim.Framework.LocklessQueue<OutgoingPacket>[] m_packetOutboxes = new OpenSim.Framework.LocklessQueue<OutgoingPacket>[THROTTLE_CATEGORY_COUNT]; | ||
150 | /// <summary>A container that can hold one packet for each outbox, used to store | ||
151 | /// dequeued packets that are being held for throttling</summary> | ||
152 | private readonly OutgoingPacket[] m_nextPackets = new OutgoingPacket[THROTTLE_CATEGORY_COUNT]; | ||
153 | /// <summary>A reference to the LLUDPServer that is managing this client</summary> | ||
154 | private readonly LLUDPServer m_udpServer; | ||
155 | |||
156 | /// <summary>Caches packed throttle information</summary> | ||
157 | private byte[] m_packedThrottles; | ||
158 | |||
159 | private int m_defaultRTO = 1000; // 1sec is the recommendation in the RFC | ||
160 | private int m_maxRTO = 60000; | ||
161 | |||
162 | /// <summary> | ||
163 | /// Default constructor | ||
164 | /// </summary> | ||
165 | /// <param name="server">Reference to the UDP server this client is connected to</param> | ||
166 | /// <param name="rates">Default throttling rates and maximum throttle limits</param> | ||
167 | /// <param name="parentThrottle">Parent HTB (hierarchical token bucket) | ||
168 | /// that the child throttles will be governed by</param> | ||
169 | /// <param name="circuitCode">Circuit code for this connection</param> | ||
170 | /// <param name="agentID">AgentID for the connected agent</param> | ||
171 | /// <param name="remoteEndPoint">Remote endpoint for this connection</param> | ||
172 | public LLUDPClient(LLUDPServer server, ThrottleRates rates, TokenBucket parentThrottle, uint circuitCode, UUID agentID, IPEndPoint remoteEndPoint, int defaultRTO, int maxRTO) | ||
173 | { | ||
174 | AgentID = agentID; | ||
175 | RemoteEndPoint = remoteEndPoint; | ||
176 | CircuitCode = circuitCode; | ||
177 | m_udpServer = server; | ||
178 | if (defaultRTO != 0) | ||
179 | m_defaultRTO = defaultRTO; | ||
180 | if (maxRTO != 0) | ||
181 | m_maxRTO = maxRTO; | ||
182 | |||
183 | // Create a token bucket throttle for this client that has the scene token bucket as a parent | ||
184 | m_throttleClient = new AdaptiveTokenBucket(parentThrottle, rates.Total, rates.AdaptiveThrottlesEnabled); | ||
185 | // Create a token bucket throttle for the total categary with the client bucket as a throttle | ||
186 | m_throttleCategory = new TokenBucket(m_throttleClient, 0); | ||
187 | // Create an array of token buckets for this clients different throttle categories | ||
188 | m_throttleCategories = new TokenBucket[THROTTLE_CATEGORY_COUNT]; | ||
189 | |||
190 | for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) | ||
191 | { | ||
192 | ThrottleOutPacketType type = (ThrottleOutPacketType)i; | ||
193 | |||
194 | // Initialize the packet outboxes, where packets sit while they are waiting for tokens | ||
195 | m_packetOutboxes[i] = new OpenSim.Framework.LocklessQueue<OutgoingPacket>(); | ||
196 | // Initialize the token buckets that control the throttling for each category | ||
197 | m_throttleCategories[i] = new TokenBucket(m_throttleCategory, rates.GetRate(type)); | ||
198 | } | ||
199 | |||
200 | // Default the retransmission timeout to three seconds | ||
201 | RTO = m_defaultRTO; | ||
202 | |||
203 | // Initialize this to a sane value to prevent early disconnects | ||
204 | TickLastPacketReceived = Environment.TickCount & Int32.MaxValue; | ||
205 | } | ||
206 | |||
207 | /// <summary> | ||
208 | /// Shuts down this client connection | ||
209 | /// </summary> | ||
210 | public void Shutdown() | ||
211 | { | ||
212 | IsConnected = false; | ||
213 | for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) | ||
214 | { | ||
215 | m_packetOutboxes[i].Clear(); | ||
216 | m_nextPackets[i] = null; | ||
217 | } | ||
218 | |||
219 | // pull the throttle out of the scene throttle | ||
220 | m_throttleClient.Parent.UnregisterRequest(m_throttleClient); | ||
221 | OnPacketStats = null; | ||
222 | OnQueueEmpty = null; | ||
223 | } | ||
224 | |||
225 | /// <summary> | ||
226 | /// Gets information about this client connection | ||
227 | /// </summary> | ||
228 | /// <returns>Information about the client connection</returns> | ||
229 | public ClientInfo GetClientInfo() | ||
230 | { | ||
231 | // TODO: This data structure is wrong in so many ways. Locking and copying the entire lists | ||
232 | // of pending and needed ACKs for every client every time some method wants information about | ||
233 | // this connection is a recipe for poor performance | ||
234 | ClientInfo info = new ClientInfo(); | ||
235 | info.pendingAcks = new Dictionary<uint, uint>(); | ||
236 | info.needAck = new Dictionary<uint, byte[]>(); | ||
237 | |||
238 | info.resendThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Resend].DripRate; | ||
239 | info.landThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Land].DripRate; | ||
240 | info.windThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate; | ||
241 | info.cloudThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate; | ||
242 | info.taskThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Task].DripRate; | ||
243 | info.assetThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate; | ||
244 | info.textureThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate; | ||
245 | info.totalThrottle = (int)m_throttleCategory.DripRate; | ||
246 | |||
247 | return info; | ||
248 | } | ||
249 | |||
250 | /// <summary> | ||
251 | /// Modifies the UDP throttles | ||
252 | /// </summary> | ||
253 | /// <param name="info">New throttling values</param> | ||
254 | public void SetClientInfo(ClientInfo info) | ||
255 | { | ||
256 | // TODO: Allowing throttles to be manually set from this function seems like a reasonable | ||
257 | // idea. On the other hand, letting external code manipulate our ACK accounting is not | ||
258 | // going to happen | ||
259 | throw new NotImplementedException(); | ||
260 | } | ||
261 | |||
262 | /// <summary> | ||
263 | /// Return statistics information about client packet queues. | ||
264 | /// </summary> | ||
265 | /// | ||
266 | /// FIXME: This should really be done in a more sensible manner rather than sending back a formatted string. | ||
267 | /// | ||
268 | /// <returns></returns> | ||
269 | public string GetStats() | ||
270 | { | ||
271 | return string.Format( | ||
272 | "{0,7} {1,7} {2,7} {3,9} {4,7} {5,7} {6,7} {7,7} {8,7} {9,8} {10,7} {11,7}", | ||
273 | PacketsReceived, | ||
274 | PacketsSent, | ||
275 | PacketsResent, | ||
276 | UnackedBytes, | ||
277 | m_packetOutboxes[(int)ThrottleOutPacketType.Resend].Count, | ||
278 | m_packetOutboxes[(int)ThrottleOutPacketType.Land].Count, | ||
279 | m_packetOutboxes[(int)ThrottleOutPacketType.Wind].Count, | ||
280 | m_packetOutboxes[(int)ThrottleOutPacketType.Cloud].Count, | ||
281 | m_packetOutboxes[(int)ThrottleOutPacketType.Task].Count, | ||
282 | m_packetOutboxes[(int)ThrottleOutPacketType.Texture].Count, | ||
283 | m_packetOutboxes[(int)ThrottleOutPacketType.Asset].Count, | ||
284 | m_packetOutboxes[(int)ThrottleOutPacketType.State].Count); | ||
285 | } | ||
286 | |||
287 | public void SendPacketStats() | ||
288 | { | ||
289 | PacketStats callback = OnPacketStats; | ||
290 | if (callback != null) | ||
291 | { | ||
292 | int newPacketsReceived = PacketsReceived - m_packetsReceivedReported; | ||
293 | int newPacketsSent = PacketsSent - m_packetsSentReported; | ||
294 | |||
295 | callback(newPacketsReceived, newPacketsSent, UnackedBytes); | ||
296 | |||
297 | m_packetsReceivedReported += newPacketsReceived; | ||
298 | m_packetsSentReported += newPacketsSent; | ||
299 | } | ||
300 | } | ||
301 | |||
302 | public void SetThrottles(byte[] throttleData) | ||
303 | { | ||
304 | byte[] adjData; | ||
305 | int pos = 0; | ||
306 | |||
307 | if (!BitConverter.IsLittleEndian) | ||
308 | { | ||
309 | byte[] newData = new byte[7 * 4]; | ||
310 | Buffer.BlockCopy(throttleData, 0, newData, 0, 7 * 4); | ||
311 | |||
312 | for (int i = 0; i < 7; i++) | ||
313 | Array.Reverse(newData, i * 4, 4); | ||
314 | |||
315 | adjData = newData; | ||
316 | } | ||
317 | else | ||
318 | { | ||
319 | adjData = throttleData; | ||
320 | } | ||
321 | |||
322 | // 0.125f converts from bits to bytes | ||
323 | int resend = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
324 | int land = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
325 | int wind = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
326 | int cloud = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
327 | int task = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
328 | int texture = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
329 | int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); | ||
330 | // State is a subcategory of task that we allocate a percentage to | ||
331 | int state = 0; | ||
332 | |||
333 | // Make sure none of the throttles are set below our packet MTU, | ||
334 | // otherwise a throttle could become permanently clogged | ||
335 | resend = Math.Max(resend, LLUDPServer.MTU); | ||
336 | land = Math.Max(land, LLUDPServer.MTU); | ||
337 | wind = Math.Max(wind, LLUDPServer.MTU); | ||
338 | cloud = Math.Max(cloud, LLUDPServer.MTU); | ||
339 | task = Math.Max(task, LLUDPServer.MTU); | ||
340 | texture = Math.Max(texture, LLUDPServer.MTU); | ||
341 | asset = Math.Max(asset, LLUDPServer.MTU); | ||
342 | |||
343 | //int total = resend + land + wind + cloud + task + texture + asset; | ||
344 | //m_log.DebugFormat("[LLUDPCLIENT]: {0} is setting throttles. Resend={1}, Land={2}, Wind={3}, Cloud={4}, Task={5}, Texture={6}, Asset={7}, Total={8}", | ||
345 | // AgentID, resend, land, wind, cloud, task, texture, asset, total); | ||
346 | |||
347 | // Update the token buckets with new throttle values | ||
348 | TokenBucket bucket; | ||
349 | |||
350 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Resend]; | ||
351 | bucket.RequestedDripRate = resend; | ||
352 | |||
353 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Land]; | ||
354 | bucket.RequestedDripRate = land; | ||
355 | |||
356 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Wind]; | ||
357 | bucket.RequestedDripRate = wind; | ||
358 | |||
359 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Cloud]; | ||
360 | bucket.RequestedDripRate = cloud; | ||
361 | |||
362 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Asset]; | ||
363 | bucket.RequestedDripRate = asset; | ||
364 | |||
365 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Task]; | ||
366 | bucket.RequestedDripRate = task; | ||
367 | |||
368 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.State]; | ||
369 | bucket.RequestedDripRate = state; | ||
370 | |||
371 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Texture]; | ||
372 | bucket.RequestedDripRate = texture; | ||
373 | |||
374 | // Reset the packed throttles cached data | ||
375 | m_packedThrottles = null; | ||
376 | } | ||
377 | |||
378 | public byte[] GetThrottlesPacked(float multiplier) | ||
379 | { | ||
380 | byte[] data = m_packedThrottles; | ||
381 | |||
382 | if (data == null) | ||
383 | { | ||
384 | float rate; | ||
385 | |||
386 | data = new byte[7 * 4]; | ||
387 | int i = 0; | ||
388 | |||
389 | // multiply by 8 to convert bytes back to bits | ||
390 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Resend].RequestedDripRate * 8 * multiplier; | ||
391 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
392 | |||
393 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Land].RequestedDripRate * 8 * multiplier; | ||
394 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
395 | |||
396 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Wind].RequestedDripRate * 8 * multiplier; | ||
397 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
398 | |||
399 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].RequestedDripRate * 8 * multiplier; | ||
400 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
401 | |||
402 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Task].RequestedDripRate * 8 * multiplier; | ||
403 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
404 | |||
405 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Texture].RequestedDripRate * 8 * multiplier; | ||
406 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
407 | |||
408 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Asset].RequestedDripRate * 8 * multiplier; | ||
409 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
410 | |||
411 | m_packedThrottles = data; | ||
412 | } | ||
413 | |||
414 | return data; | ||
415 | } | ||
416 | |||
417 | /// <summary> | ||
418 | /// Queue an outgoing packet if appropriate. | ||
419 | /// </summary> | ||
420 | /// <param name="packet"></param> | ||
421 | /// <param name="forceQueue">Always queue the packet if at all possible.</param> | ||
422 | /// <returns> | ||
423 | /// true if the packet has been queued, | ||
424 | /// false if the packet has not been queued and should be sent immediately. | ||
425 | /// </returns> | ||
426 | public bool EnqueueOutgoing(OutgoingPacket packet, bool forceQueue) | ||
427 | { | ||
428 | int category = (int)packet.Category; | ||
429 | |||
430 | if (category >= 0 && category < m_packetOutboxes.Length) | ||
431 | { | ||
432 | OpenSim.Framework.LocklessQueue<OutgoingPacket> queue = m_packetOutboxes[category]; | ||
433 | TokenBucket bucket = m_throttleCategories[category]; | ||
434 | |||
435 | // Don't send this packet if there is already a packet waiting in the queue | ||
436 | // even if we have the tokens to send it, tokens should go to the already | ||
437 | // queued packets | ||
438 | if (queue.Count > 0) | ||
439 | { | ||
440 | queue.Enqueue(packet); | ||
441 | return true; | ||
442 | } | ||
443 | |||
444 | |||
445 | if (!forceQueue && bucket.RemoveTokens(packet.Buffer.DataLength)) | ||
446 | { | ||
447 | // Enough tokens were removed from the bucket, the packet will not be queued | ||
448 | return false; | ||
449 | } | ||
450 | else | ||
451 | { | ||
452 | // Force queue specified or not enough tokens in the bucket, queue this packet | ||
453 | queue.Enqueue(packet); | ||
454 | return true; | ||
455 | } | ||
456 | } | ||
457 | else | ||
458 | { | ||
459 | // We don't have a token bucket for this category, so it will not be queued | ||
460 | return false; | ||
461 | } | ||
462 | } | ||
463 | |||
464 | /// <summary> | ||
465 | /// Loops through all of the packet queues for this client and tries to send | ||
466 | /// an outgoing packet from each, obeying the throttling bucket limits | ||
467 | /// </summary> | ||
468 | /// | ||
469 | /// <remarks> | ||
470 | /// Packet queues are inspected in ascending numerical order starting from 0. Therefore, queues with a lower | ||
471 | /// ThrottleOutPacketType number will see their packet get sent first (e.g. if both Land and Wind queues have | ||
472 | /// packets, then the packet at the front of the Land queue will be sent before the packet at the front of the | ||
473 | /// wind queue). | ||
474 | /// | ||
475 | /// This function is only called from a synchronous loop in the | ||
476 | /// UDPServer so we don't need to bother making this thread safe | ||
477 | /// </remarks> | ||
478 | /// | ||
479 | /// <returns>True if any packets were sent, otherwise false</returns> | ||
480 | public bool DequeueOutgoing() | ||
481 | { | ||
482 | OutgoingPacket packet; | ||
483 | OpenSim.Framework.LocklessQueue<OutgoingPacket> queue; | ||
484 | TokenBucket bucket; | ||
485 | bool packetSent = false; | ||
486 | ThrottleOutPacketTypeFlags emptyCategories = 0; | ||
487 | |||
488 | //string queueDebugOutput = String.Empty; // Serious debug business | ||
489 | |||
490 | for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) | ||
491 | { | ||
492 | bucket = m_throttleCategories[i]; | ||
493 | //queueDebugOutput += m_packetOutboxes[i].Count + " "; // Serious debug business | ||
494 | |||
495 | if (m_nextPackets[i] != null) | ||
496 | { | ||
497 | // This bucket was empty the last time we tried to send a packet, | ||
498 | // leaving a dequeued packet still waiting to be sent out. Try to | ||
499 | // send it again | ||
500 | OutgoingPacket nextPacket = m_nextPackets[i]; | ||
501 | if (bucket.RemoveTokens(nextPacket.Buffer.DataLength)) | ||
502 | { | ||
503 | // Send the packet | ||
504 | m_udpServer.SendPacketFinal(nextPacket); | ||
505 | m_nextPackets[i] = null; | ||
506 | packetSent = true; | ||
507 | } | ||
508 | } | ||
509 | else | ||
510 | { | ||
511 | // No dequeued packet waiting to be sent, try to pull one off | ||
512 | // this queue | ||
513 | queue = m_packetOutboxes[i]; | ||
514 | if (queue.Dequeue(out packet)) | ||
515 | { | ||
516 | // A packet was pulled off the queue. See if we have | ||
517 | // enough tokens in the bucket to send it out | ||
518 | if (bucket.RemoveTokens(packet.Buffer.DataLength)) | ||
519 | { | ||
520 | // Send the packet | ||
521 | m_udpServer.SendPacketFinal(packet); | ||
522 | packetSent = true; | ||
523 | } | ||
524 | else | ||
525 | { | ||
526 | // Save the dequeued packet for the next iteration | ||
527 | m_nextPackets[i] = packet; | ||
528 | } | ||
529 | |||
530 | // If the queue is empty after this dequeue, fire the queue | ||
531 | // empty callback now so it has a chance to fill before we | ||
532 | // get back here | ||
533 | if (queue.Count == 0) | ||
534 | emptyCategories |= CategoryToFlag(i); | ||
535 | } | ||
536 | else | ||
537 | { | ||
538 | // No packets in this queue. Fire the queue empty callback | ||
539 | // if it has not been called recently | ||
540 | emptyCategories |= CategoryToFlag(i); | ||
541 | } | ||
542 | } | ||
543 | } | ||
544 | |||
545 | if (emptyCategories != 0) | ||
546 | BeginFireQueueEmpty(emptyCategories); | ||
547 | |||
548 | //m_log.Info("[LLUDPCLIENT]: Queues: " + queueDebugOutput); // Serious debug business | ||
549 | return packetSent; | ||
550 | } | ||
551 | |||
552 | /// <summary> | ||
553 | /// Called when an ACK packet is received and a round-trip time for a | ||
554 | /// packet is calculated. This is used to calculate the smoothed | ||
555 | /// round-trip time, round trip time variance, and finally the | ||
556 | /// retransmission timeout | ||
557 | /// </summary> | ||
558 | /// <param name="r">Round-trip time of a single packet and its | ||
559 | /// acknowledgement</param> | ||
560 | public void UpdateRoundTrip(float r) | ||
561 | { | ||
562 | const float ALPHA = 0.125f; | ||
563 | const float BETA = 0.25f; | ||
564 | const float K = 4.0f; | ||
565 | |||
566 | if (RTTVAR == 0.0f) | ||
567 | { | ||
568 | // First RTT measurement | ||
569 | SRTT = r; | ||
570 | RTTVAR = r * 0.5f; | ||
571 | } | ||
572 | else | ||
573 | { | ||
574 | // Subsequence RTT measurement | ||
575 | RTTVAR = (1.0f - BETA) * RTTVAR + BETA * Math.Abs(SRTT - r); | ||
576 | SRTT = (1.0f - ALPHA) * SRTT + ALPHA * r; | ||
577 | } | ||
578 | |||
579 | int rto = (int)(SRTT + Math.Max(m_udpServer.TickCountResolution, K * RTTVAR)); | ||
580 | |||
581 | // Clamp the retransmission timeout to manageable values | ||
582 | rto = Utils.Clamp(rto, m_defaultRTO, m_maxRTO); | ||
583 | |||
584 | RTO = rto; | ||
585 | |||
586 | //m_log.Debug("[LLUDPCLIENT]: Setting agent " + this.Agent.FullName + "'s RTO to " + RTO + "ms with an RTTVAR of " + | ||
587 | // RTTVAR + " based on new RTT of " + r + "ms"); | ||
588 | } | ||
589 | |||
590 | /// <summary> | ||
591 | /// Exponential backoff of the retransmission timeout, per section 5.5 | ||
592 | /// of RFC 2988 | ||
593 | /// </summary> | ||
594 | public void BackoffRTO() | ||
595 | { | ||
596 | // Reset SRTT and RTTVAR, we assume they are bogus since things | ||
597 | // didn't work out and we're backing off the timeout | ||
598 | SRTT = 0.0f; | ||
599 | RTTVAR = 0.0f; | ||
600 | |||
601 | // Double the retransmission timeout | ||
602 | RTO = Math.Min(RTO * 2, m_maxRTO); | ||
603 | } | ||
604 | |||
605 | /// <summary> | ||
606 | /// Does an early check to see if this queue empty callback is already | ||
607 | /// running, then asynchronously firing the event | ||
608 | /// </summary> | ||
609 | /// <param name="throttleIndex">Throttle category to fire the callback | ||
610 | /// for</param> | ||
611 | private void BeginFireQueueEmpty(ThrottleOutPacketTypeFlags categories) | ||
612 | { | ||
613 | if (m_nextOnQueueEmpty != 0 && (Environment.TickCount & Int32.MaxValue) >= m_nextOnQueueEmpty) | ||
614 | { | ||
615 | // Use a value of 0 to signal that FireQueueEmpty is running | ||
616 | m_nextOnQueueEmpty = 0; | ||
617 | // Asynchronously run the callback | ||
618 | Util.FireAndForget(FireQueueEmpty, categories); | ||
619 | } | ||
620 | } | ||
621 | |||
622 | /// <summary> | ||
623 | /// Fires the OnQueueEmpty callback and sets the minimum time that it | ||
624 | /// can be called again | ||
625 | /// </summary> | ||
626 | /// <param name="o">Throttle categories to fire the callback for, | ||
627 | /// stored as an object to match the WaitCallback delegate | ||
628 | /// signature</param> | ||
629 | private void FireQueueEmpty(object o) | ||
630 | { | ||
631 | const int MIN_CALLBACK_MS = 30; | ||
632 | |||
633 | ThrottleOutPacketTypeFlags categories = (ThrottleOutPacketTypeFlags)o; | ||
634 | QueueEmpty callback = OnQueueEmpty; | ||
635 | |||
636 | int start = Environment.TickCount & Int32.MaxValue; | ||
637 | |||
638 | if (callback != null) | ||
639 | { | ||
640 | try { callback(categories); } | ||
641 | catch (Exception e) { m_log.Error("[LLUDPCLIENT]: OnQueueEmpty(" + categories + ") threw an exception: " + e.Message, e); } | ||
642 | } | ||
643 | |||
644 | m_nextOnQueueEmpty = start + MIN_CALLBACK_MS; | ||
645 | if (m_nextOnQueueEmpty == 0) | ||
646 | m_nextOnQueueEmpty = 1; | ||
647 | } | ||
648 | |||
649 | /// <summary> | ||
650 | /// Converts a <seealso cref="ThrottleOutPacketType"/> integer to a | ||
651 | /// flag value | ||
652 | /// </summary> | ||
653 | /// <param name="i">Throttle category to convert</param> | ||
654 | /// <returns>Flag representation of the throttle category</returns> | ||
655 | private static ThrottleOutPacketTypeFlags CategoryToFlag(int i) | ||
656 | { | ||
657 | ThrottleOutPacketType category = (ThrottleOutPacketType)i; | ||
658 | |||
659 | /* | ||
660 | * Land = 1, | ||
661 | /// <summary>Wind data</summary> | ||
662 | Wind = 2, | ||
663 | /// <summary>Cloud data</summary> | ||
664 | Cloud = 3, | ||
665 | /// <summary>Any packets that do not fit into the other throttles</summary> | ||
666 | Task = 4, | ||
667 | /// <summary>Texture assets</summary> | ||
668 | Texture = 5, | ||
669 | /// <summary>Non-texture assets</summary> | ||
670 | Asset = 6, | ||
671 | /// <summary>Avatar and primitive data</summary> | ||
672 | /// <remarks>This is a sub-category of Task</remarks> | ||
673 | State = 7, | ||
674 | */ | ||
675 | |||
676 | switch (category) | ||
677 | { | ||
678 | case ThrottleOutPacketType.Land: | ||
679 | return ThrottleOutPacketTypeFlags.Land; | ||
680 | case ThrottleOutPacketType.Wind: | ||
681 | return ThrottleOutPacketTypeFlags.Wind; | ||
682 | case ThrottleOutPacketType.Cloud: | ||
683 | return ThrottleOutPacketTypeFlags.Cloud; | ||
684 | case ThrottleOutPacketType.Task: | ||
685 | return ThrottleOutPacketTypeFlags.Task; | ||
686 | case ThrottleOutPacketType.Texture: | ||
687 | return ThrottleOutPacketTypeFlags.Texture; | ||
688 | case ThrottleOutPacketType.Asset: | ||
689 | return ThrottleOutPacketTypeFlags.Asset; | ||
690 | case ThrottleOutPacketType.State: | ||
691 | return ThrottleOutPacketTypeFlags.State; | ||
692 | default: | ||
693 | return 0; | ||
694 | } | ||
695 | } | ||
696 | } | ||
697 | } | ||