diff options
author | Melanie | 2011-05-05 09:49:10 +0100 |
---|---|---|
committer | Melanie | 2011-05-05 09:49:10 +0100 |
commit | 4beb0c9b9b402c1e8f70a02c0efa557c1f292f1c (patch) | |
tree | 24e24cb7b424df74b13788d28be9d7588fbd99d0 /OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs | |
parent | Merge branch 'master' into careminster-presence-refactor (diff) | |
parent | Test m_Enabled on RemoveRegion. (diff) | |
download | opensim-SC_OLD-4beb0c9b9b402c1e8f70a02c0efa557c1f292f1c.zip opensim-SC_OLD-4beb0c9b9b402c1e8f70a02c0efa557c1f292f1c.tar.gz opensim-SC_OLD-4beb0c9b9b402c1e8f70a02c0efa557c1f292f1c.tar.bz2 opensim-SC_OLD-4beb0c9b9b402c1e8f70a02c0efa557c1f292f1c.tar.xz |
Merge branch 'master' into careminster-presence-refactor
Diffstat (limited to 'OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs')
-rw-r--r-- | OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs | 707 |
1 files changed, 707 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..95a8e23 --- /dev/null +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs | |||
@@ -0,0 +1,707 @@ | |||
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 | public bool m_deliverPackets = true; | ||
162 | |||
163 | /// <summary> | ||
164 | /// Default constructor | ||
165 | /// </summary> | ||
166 | /// <param name="server">Reference to the UDP server this client is connected to</param> | ||
167 | /// <param name="rates">Default throttling rates and maximum throttle limits</param> | ||
168 | /// <param name="parentThrottle">Parent HTB (hierarchical token bucket) | ||
169 | /// that the child throttles will be governed by</param> | ||
170 | /// <param name="circuitCode">Circuit code for this connection</param> | ||
171 | /// <param name="agentID">AgentID for the connected agent</param> | ||
172 | /// <param name="remoteEndPoint">Remote endpoint for this connection</param> | ||
173 | public LLUDPClient(LLUDPServer server, ThrottleRates rates, TokenBucket parentThrottle, uint circuitCode, UUID agentID, IPEndPoint remoteEndPoint, int defaultRTO, int maxRTO) | ||
174 | { | ||
175 | AgentID = agentID; | ||
176 | RemoteEndPoint = remoteEndPoint; | ||
177 | CircuitCode = circuitCode; | ||
178 | m_udpServer = server; | ||
179 | if (defaultRTO != 0) | ||
180 | m_defaultRTO = defaultRTO; | ||
181 | if (maxRTO != 0) | ||
182 | m_maxRTO = maxRTO; | ||
183 | |||
184 | // Create a token bucket throttle for this client that has the scene token bucket as a parent | ||
185 | m_throttleClient = new AdaptiveTokenBucket(parentThrottle, rates.Total, rates.AdaptiveThrottlesEnabled); | ||
186 | // Create a token bucket throttle for the total categary with the client bucket as a throttle | ||
187 | m_throttleCategory = new TokenBucket(m_throttleClient, 0); | ||
188 | // Create an array of token buckets for this clients different throttle categories | ||
189 | m_throttleCategories = new TokenBucket[THROTTLE_CATEGORY_COUNT]; | ||
190 | |||
191 | for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) | ||
192 | { | ||
193 | ThrottleOutPacketType type = (ThrottleOutPacketType)i; | ||
194 | |||
195 | // Initialize the packet outboxes, where packets sit while they are waiting for tokens | ||
196 | m_packetOutboxes[i] = new OpenSim.Framework.LocklessQueue<OutgoingPacket>(); | ||
197 | // Initialize the token buckets that control the throttling for each category | ||
198 | m_throttleCategories[i] = new TokenBucket(m_throttleCategory, rates.GetRate(type)); | ||
199 | } | ||
200 | |||
201 | // Default the retransmission timeout to three seconds | ||
202 | RTO = m_defaultRTO; | ||
203 | |||
204 | // Initialize this to a sane value to prevent early disconnects | ||
205 | TickLastPacketReceived = Environment.TickCount & Int32.MaxValue; | ||
206 | } | ||
207 | |||
208 | /// <summary> | ||
209 | /// Shuts down this client connection | ||
210 | /// </summary> | ||
211 | public void Shutdown() | ||
212 | { | ||
213 | IsConnected = false; | ||
214 | for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) | ||
215 | { | ||
216 | m_packetOutboxes[i].Clear(); | ||
217 | m_nextPackets[i] = null; | ||
218 | } | ||
219 | |||
220 | // pull the throttle out of the scene throttle | ||
221 | m_throttleClient.Parent.UnregisterRequest(m_throttleClient); | ||
222 | OnPacketStats = null; | ||
223 | OnQueueEmpty = null; | ||
224 | } | ||
225 | |||
226 | /// <summary> | ||
227 | /// Gets information about this client connection | ||
228 | /// </summary> | ||
229 | /// <returns>Information about the client connection</returns> | ||
230 | public ClientInfo GetClientInfo() | ||
231 | { | ||
232 | // TODO: This data structure is wrong in so many ways. Locking and copying the entire lists | ||
233 | // of pending and needed ACKs for every client every time some method wants information about | ||
234 | // this connection is a recipe for poor performance | ||
235 | ClientInfo info = new ClientInfo(); | ||
236 | info.pendingAcks = new Dictionary<uint, uint>(); | ||
237 | info.needAck = new Dictionary<uint, byte[]>(); | ||
238 | |||
239 | info.resendThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Resend].DripRate; | ||
240 | info.landThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Land].DripRate; | ||
241 | info.windThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate; | ||
242 | info.cloudThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate; | ||
243 | info.taskThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Task].DripRate; | ||
244 | info.assetThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate; | ||
245 | info.textureThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate; | ||
246 | info.totalThrottle = (int)m_throttleCategory.DripRate; | ||
247 | |||
248 | return info; | ||
249 | } | ||
250 | |||
251 | /// <summary> | ||
252 | /// Modifies the UDP throttles | ||
253 | /// </summary> | ||
254 | /// <param name="info">New throttling values</param> | ||
255 | public void SetClientInfo(ClientInfo info) | ||
256 | { | ||
257 | // TODO: Allowing throttles to be manually set from this function seems like a reasonable | ||
258 | // idea. On the other hand, letting external code manipulate our ACK accounting is not | ||
259 | // going to happen | ||
260 | throw new NotImplementedException(); | ||
261 | } | ||
262 | |||
263 | /// <summary> | ||
264 | /// Return statistics information about client packet queues. | ||
265 | /// </summary> | ||
266 | /// | ||
267 | /// FIXME: This should really be done in a more sensible manner rather than sending back a formatted string. | ||
268 | /// | ||
269 | /// <returns></returns> | ||
270 | public string GetStats() | ||
271 | { | ||
272 | return string.Format( | ||
273 | "{0,7} {1,7} {2,7} {3,9} {4,7} {5,7} {6,7} {7,7} {8,7} {9,8} {10,7} {11,7}", | ||
274 | PacketsReceived, | ||
275 | PacketsSent, | ||
276 | PacketsResent, | ||
277 | UnackedBytes, | ||
278 | m_packetOutboxes[(int)ThrottleOutPacketType.Resend].Count, | ||
279 | m_packetOutboxes[(int)ThrottleOutPacketType.Land].Count, | ||
280 | m_packetOutboxes[(int)ThrottleOutPacketType.Wind].Count, | ||
281 | m_packetOutboxes[(int)ThrottleOutPacketType.Cloud].Count, | ||
282 | m_packetOutboxes[(int)ThrottleOutPacketType.Task].Count, | ||
283 | m_packetOutboxes[(int)ThrottleOutPacketType.Texture].Count, | ||
284 | m_packetOutboxes[(int)ThrottleOutPacketType.Asset].Count, | ||
285 | m_packetOutboxes[(int)ThrottleOutPacketType.State].Count); | ||
286 | } | ||
287 | |||
288 | public void SendPacketStats() | ||
289 | { | ||
290 | PacketStats callback = OnPacketStats; | ||
291 | if (callback != null) | ||
292 | { | ||
293 | int newPacketsReceived = PacketsReceived - m_packetsReceivedReported; | ||
294 | int newPacketsSent = PacketsSent - m_packetsSentReported; | ||
295 | |||
296 | callback(newPacketsReceived, newPacketsSent, UnackedBytes); | ||
297 | |||
298 | m_packetsReceivedReported += newPacketsReceived; | ||
299 | m_packetsSentReported += newPacketsSent; | ||
300 | } | ||
301 | } | ||
302 | |||
303 | public void SetThrottles(byte[] throttleData) | ||
304 | { | ||
305 | byte[] adjData; | ||
306 | int pos = 0; | ||
307 | |||
308 | if (!BitConverter.IsLittleEndian) | ||
309 | { | ||
310 | byte[] newData = new byte[7 * 4]; | ||
311 | Buffer.BlockCopy(throttleData, 0, newData, 0, 7 * 4); | ||
312 | |||
313 | for (int i = 0; i < 7; i++) | ||
314 | Array.Reverse(newData, i * 4, 4); | ||
315 | |||
316 | adjData = newData; | ||
317 | } | ||
318 | else | ||
319 | { | ||
320 | adjData = throttleData; | ||
321 | } | ||
322 | |||
323 | // 0.125f converts from bits to bytes | ||
324 | int resend = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
325 | int land = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
326 | int wind = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
327 | int cloud = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
328 | int task = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
329 | int texture = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
330 | int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); | ||
331 | // State is a subcategory of task that we allocate a percentage to | ||
332 | int state = 0; | ||
333 | |||
334 | // Make sure none of the throttles are set below our packet MTU, | ||
335 | // otherwise a throttle could become permanently clogged | ||
336 | resend = Math.Max(resend, LLUDPServer.MTU); | ||
337 | land = Math.Max(land, LLUDPServer.MTU); | ||
338 | wind = Math.Max(wind, LLUDPServer.MTU); | ||
339 | cloud = Math.Max(cloud, LLUDPServer.MTU); | ||
340 | task = Math.Max(task, LLUDPServer.MTU); | ||
341 | texture = Math.Max(texture, LLUDPServer.MTU); | ||
342 | asset = Math.Max(asset, LLUDPServer.MTU); | ||
343 | |||
344 | //int total = resend + land + wind + cloud + task + texture + asset; | ||
345 | //m_log.DebugFormat("[LLUDPCLIENT]: {0} is setting throttles. Resend={1}, Land={2}, Wind={3}, Cloud={4}, Task={5}, Texture={6}, Asset={7}, Total={8}", | ||
346 | // AgentID, resend, land, wind, cloud, task, texture, asset, total); | ||
347 | |||
348 | // Update the token buckets with new throttle values | ||
349 | TokenBucket bucket; | ||
350 | |||
351 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Resend]; | ||
352 | bucket.RequestedDripRate = resend; | ||
353 | |||
354 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Land]; | ||
355 | bucket.RequestedDripRate = land; | ||
356 | |||
357 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Wind]; | ||
358 | bucket.RequestedDripRate = wind; | ||
359 | |||
360 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Cloud]; | ||
361 | bucket.RequestedDripRate = cloud; | ||
362 | |||
363 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Asset]; | ||
364 | bucket.RequestedDripRate = asset; | ||
365 | |||
366 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Task]; | ||
367 | bucket.RequestedDripRate = task; | ||
368 | |||
369 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.State]; | ||
370 | bucket.RequestedDripRate = state; | ||
371 | |||
372 | bucket = m_throttleCategories[(int)ThrottleOutPacketType.Texture]; | ||
373 | bucket.RequestedDripRate = texture; | ||
374 | |||
375 | // Reset the packed throttles cached data | ||
376 | m_packedThrottles = null; | ||
377 | } | ||
378 | |||
379 | public byte[] GetThrottlesPacked(float multiplier) | ||
380 | { | ||
381 | byte[] data = m_packedThrottles; | ||
382 | |||
383 | if (data == null) | ||
384 | { | ||
385 | float rate; | ||
386 | |||
387 | data = new byte[7 * 4]; | ||
388 | int i = 0; | ||
389 | |||
390 | // multiply by 8 to convert bytes back to bits | ||
391 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Resend].RequestedDripRate * 8 * multiplier; | ||
392 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
393 | |||
394 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Land].RequestedDripRate * 8 * multiplier; | ||
395 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
396 | |||
397 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Wind].RequestedDripRate * 8 * multiplier; | ||
398 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
399 | |||
400 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].RequestedDripRate * 8 * multiplier; | ||
401 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
402 | |||
403 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Task].RequestedDripRate * 8 * multiplier; | ||
404 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
405 | |||
406 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Texture].RequestedDripRate * 8 * multiplier; | ||
407 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
408 | |||
409 | rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Asset].RequestedDripRate * 8 * multiplier; | ||
410 | Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4; | ||
411 | |||
412 | m_packedThrottles = data; | ||
413 | } | ||
414 | |||
415 | return data; | ||
416 | } | ||
417 | |||
418 | /// <summary> | ||
419 | /// Queue an outgoing packet if appropriate. | ||
420 | /// </summary> | ||
421 | /// <param name="packet"></param> | ||
422 | /// <param name="forceQueue">Always queue the packet if at all possible.</param> | ||
423 | /// <returns> | ||
424 | /// true if the packet has been queued, | ||
425 | /// false if the packet has not been queued and should be sent immediately. | ||
426 | /// </returns> | ||
427 | public bool EnqueueOutgoing(OutgoingPacket packet, bool forceQueue) | ||
428 | { | ||
429 | int category = (int)packet.Category; | ||
430 | |||
431 | if (category >= 0 && category < m_packetOutboxes.Length) | ||
432 | { | ||
433 | OpenSim.Framework.LocklessQueue<OutgoingPacket> queue = m_packetOutboxes[category]; | ||
434 | |||
435 | if (m_deliverPackets == false) | ||
436 | { | ||
437 | queue.Enqueue(packet); | ||
438 | return true; | ||
439 | } | ||
440 | |||
441 | TokenBucket bucket = m_throttleCategories[category]; | ||
442 | |||
443 | // Don't send this packet if there is already a packet waiting in the queue | ||
444 | // even if we have the tokens to send it, tokens should go to the already | ||
445 | // queued packets | ||
446 | if (queue.Count > 0) | ||
447 | { | ||
448 | queue.Enqueue(packet); | ||
449 | return true; | ||
450 | } | ||
451 | |||
452 | |||
453 | if (!forceQueue && bucket.RemoveTokens(packet.Buffer.DataLength)) | ||
454 | { | ||
455 | // Enough tokens were removed from the bucket, the packet will not be queued | ||
456 | return false; | ||
457 | } | ||
458 | else | ||
459 | { | ||
460 | // Force queue specified or not enough tokens in the bucket, queue this packet | ||
461 | queue.Enqueue(packet); | ||
462 | return true; | ||
463 | } | ||
464 | } | ||
465 | else | ||
466 | { | ||
467 | // We don't have a token bucket for this category, so it will not be queued | ||
468 | return false; | ||
469 | } | ||
470 | } | ||
471 | |||
472 | /// <summary> | ||
473 | /// Loops through all of the packet queues for this client and tries to send | ||
474 | /// an outgoing packet from each, obeying the throttling bucket limits | ||
475 | /// </summary> | ||
476 | /// | ||
477 | /// <remarks> | ||
478 | /// Packet queues are inspected in ascending numerical order starting from 0. Therefore, queues with a lower | ||
479 | /// ThrottleOutPacketType number will see their packet get sent first (e.g. if both Land and Wind queues have | ||
480 | /// packets, then the packet at the front of the Land queue will be sent before the packet at the front of the | ||
481 | /// wind queue). | ||
482 | /// | ||
483 | /// This function is only called from a synchronous loop in the | ||
484 | /// UDPServer so we don't need to bother making this thread safe | ||
485 | /// </remarks> | ||
486 | /// | ||
487 | /// <returns>True if any packets were sent, otherwise false</returns> | ||
488 | public bool DequeueOutgoing() | ||
489 | { | ||
490 | if (m_deliverPackets == false) return false; | ||
491 | |||
492 | OutgoingPacket packet; | ||
493 | OpenSim.Framework.LocklessQueue<OutgoingPacket> queue; | ||
494 | TokenBucket bucket; | ||
495 | bool packetSent = false; | ||
496 | ThrottleOutPacketTypeFlags emptyCategories = 0; | ||
497 | |||
498 | //string queueDebugOutput = String.Empty; // Serious debug business | ||
499 | |||
500 | for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) | ||
501 | { | ||
502 | bucket = m_throttleCategories[i]; | ||
503 | //queueDebugOutput += m_packetOutboxes[i].Count + " "; // Serious debug business | ||
504 | |||
505 | if (m_nextPackets[i] != null) | ||
506 | { | ||
507 | // This bucket was empty the last time we tried to send a packet, | ||
508 | // leaving a dequeued packet still waiting to be sent out. Try to | ||
509 | // send it again | ||
510 | OutgoingPacket nextPacket = m_nextPackets[i]; | ||
511 | if (bucket.RemoveTokens(nextPacket.Buffer.DataLength)) | ||
512 | { | ||
513 | // Send the packet | ||
514 | m_udpServer.SendPacketFinal(nextPacket); | ||
515 | m_nextPackets[i] = null; | ||
516 | packetSent = true; | ||
517 | } | ||
518 | } | ||
519 | else | ||
520 | { | ||
521 | // No dequeued packet waiting to be sent, try to pull one off | ||
522 | // this queue | ||
523 | queue = m_packetOutboxes[i]; | ||
524 | if (queue.Dequeue(out packet)) | ||
525 | { | ||
526 | // A packet was pulled off the queue. See if we have | ||
527 | // enough tokens in the bucket to send it out | ||
528 | if (bucket.RemoveTokens(packet.Buffer.DataLength)) | ||
529 | { | ||
530 | // Send the packet | ||
531 | m_udpServer.SendPacketFinal(packet); | ||
532 | packetSent = true; | ||
533 | } | ||
534 | else | ||
535 | { | ||
536 | // Save the dequeued packet for the next iteration | ||
537 | m_nextPackets[i] = packet; | ||
538 | } | ||
539 | |||
540 | // If the queue is empty after this dequeue, fire the queue | ||
541 | // empty callback now so it has a chance to fill before we | ||
542 | // get back here | ||
543 | if (queue.Count == 0) | ||
544 | emptyCategories |= CategoryToFlag(i); | ||
545 | } | ||
546 | else | ||
547 | { | ||
548 | // No packets in this queue. Fire the queue empty callback | ||
549 | // if it has not been called recently | ||
550 | emptyCategories |= CategoryToFlag(i); | ||
551 | } | ||
552 | } | ||
553 | } | ||
554 | |||
555 | if (emptyCategories != 0) | ||
556 | BeginFireQueueEmpty(emptyCategories); | ||
557 | |||
558 | //m_log.Info("[LLUDPCLIENT]: Queues: " + queueDebugOutput); // Serious debug business | ||
559 | return packetSent; | ||
560 | } | ||
561 | |||
562 | /// <summary> | ||
563 | /// Called when an ACK packet is received and a round-trip time for a | ||
564 | /// packet is calculated. This is used to calculate the smoothed | ||
565 | /// round-trip time, round trip time variance, and finally the | ||
566 | /// retransmission timeout | ||
567 | /// </summary> | ||
568 | /// <param name="r">Round-trip time of a single packet and its | ||
569 | /// acknowledgement</param> | ||
570 | public void UpdateRoundTrip(float r) | ||
571 | { | ||
572 | const float ALPHA = 0.125f; | ||
573 | const float BETA = 0.25f; | ||
574 | const float K = 4.0f; | ||
575 | |||
576 | if (RTTVAR == 0.0f) | ||
577 | { | ||
578 | // First RTT measurement | ||
579 | SRTT = r; | ||
580 | RTTVAR = r * 0.5f; | ||
581 | } | ||
582 | else | ||
583 | { | ||
584 | // Subsequence RTT measurement | ||
585 | RTTVAR = (1.0f - BETA) * RTTVAR + BETA * Math.Abs(SRTT - r); | ||
586 | SRTT = (1.0f - ALPHA) * SRTT + ALPHA * r; | ||
587 | } | ||
588 | |||
589 | int rto = (int)(SRTT + Math.Max(m_udpServer.TickCountResolution, K * RTTVAR)); | ||
590 | |||
591 | // Clamp the retransmission timeout to manageable values | ||
592 | rto = Utils.Clamp(rto, m_defaultRTO, m_maxRTO); | ||
593 | |||
594 | RTO = rto; | ||
595 | |||
596 | //m_log.Debug("[LLUDPCLIENT]: Setting agent " + this.Agent.FullName + "'s RTO to " + RTO + "ms with an RTTVAR of " + | ||
597 | // RTTVAR + " based on new RTT of " + r + "ms"); | ||
598 | } | ||
599 | |||
600 | /// <summary> | ||
601 | /// Exponential backoff of the retransmission timeout, per section 5.5 | ||
602 | /// of RFC 2988 | ||
603 | /// </summary> | ||
604 | public void BackoffRTO() | ||
605 | { | ||
606 | // Reset SRTT and RTTVAR, we assume they are bogus since things | ||
607 | // didn't work out and we're backing off the timeout | ||
608 | SRTT = 0.0f; | ||
609 | RTTVAR = 0.0f; | ||
610 | |||
611 | // Double the retransmission timeout | ||
612 | RTO = Math.Min(RTO * 2, m_maxRTO); | ||
613 | } | ||
614 | |||
615 | /// <summary> | ||
616 | /// Does an early check to see if this queue empty callback is already | ||
617 | /// running, then asynchronously firing the event | ||
618 | /// </summary> | ||
619 | /// <param name="throttleIndex">Throttle category to fire the callback | ||
620 | /// for</param> | ||
621 | private void BeginFireQueueEmpty(ThrottleOutPacketTypeFlags categories) | ||
622 | { | ||
623 | if (m_nextOnQueueEmpty != 0 && (Environment.TickCount & Int32.MaxValue) >= m_nextOnQueueEmpty) | ||
624 | { | ||
625 | // Use a value of 0 to signal that FireQueueEmpty is running | ||
626 | m_nextOnQueueEmpty = 0; | ||
627 | // Asynchronously run the callback | ||
628 | Util.FireAndForget(FireQueueEmpty, categories); | ||
629 | } | ||
630 | } | ||
631 | |||
632 | /// <summary> | ||
633 | /// Fires the OnQueueEmpty callback and sets the minimum time that it | ||
634 | /// can be called again | ||
635 | /// </summary> | ||
636 | /// <param name="o">Throttle categories to fire the callback for, | ||
637 | /// stored as an object to match the WaitCallback delegate | ||
638 | /// signature</param> | ||
639 | private void FireQueueEmpty(object o) | ||
640 | { | ||
641 | const int MIN_CALLBACK_MS = 30; | ||
642 | |||
643 | ThrottleOutPacketTypeFlags categories = (ThrottleOutPacketTypeFlags)o; | ||
644 | QueueEmpty callback = OnQueueEmpty; | ||
645 | |||
646 | int start = Environment.TickCount & Int32.MaxValue; | ||
647 | |||
648 | if (callback != null) | ||
649 | { | ||
650 | try { callback(categories); } | ||
651 | catch (Exception e) { m_log.Error("[LLUDPCLIENT]: OnQueueEmpty(" + categories + ") threw an exception: " + e.Message, e); } | ||
652 | } | ||
653 | |||
654 | m_nextOnQueueEmpty = start + MIN_CALLBACK_MS; | ||
655 | if (m_nextOnQueueEmpty == 0) | ||
656 | m_nextOnQueueEmpty = 1; | ||
657 | } | ||
658 | |||
659 | /// <summary> | ||
660 | /// Converts a <seealso cref="ThrottleOutPacketType"/> integer to a | ||
661 | /// flag value | ||
662 | /// </summary> | ||
663 | /// <param name="i">Throttle category to convert</param> | ||
664 | /// <returns>Flag representation of the throttle category</returns> | ||
665 | private static ThrottleOutPacketTypeFlags CategoryToFlag(int i) | ||
666 | { | ||
667 | ThrottleOutPacketType category = (ThrottleOutPacketType)i; | ||
668 | |||
669 | /* | ||
670 | * Land = 1, | ||
671 | /// <summary>Wind data</summary> | ||
672 | Wind = 2, | ||
673 | /// <summary>Cloud data</summary> | ||
674 | Cloud = 3, | ||
675 | /// <summary>Any packets that do not fit into the other throttles</summary> | ||
676 | Task = 4, | ||
677 | /// <summary>Texture assets</summary> | ||
678 | Texture = 5, | ||
679 | /// <summary>Non-texture assets</summary> | ||
680 | Asset = 6, | ||
681 | /// <summary>Avatar and primitive data</summary> | ||
682 | /// <remarks>This is a sub-category of Task</remarks> | ||
683 | State = 7, | ||
684 | */ | ||
685 | |||
686 | switch (category) | ||
687 | { | ||
688 | case ThrottleOutPacketType.Land: | ||
689 | return ThrottleOutPacketTypeFlags.Land; | ||
690 | case ThrottleOutPacketType.Wind: | ||
691 | return ThrottleOutPacketTypeFlags.Wind; | ||
692 | case ThrottleOutPacketType.Cloud: | ||
693 | return ThrottleOutPacketTypeFlags.Cloud; | ||
694 | case ThrottleOutPacketType.Task: | ||
695 | return ThrottleOutPacketTypeFlags.Task; | ||
696 | case ThrottleOutPacketType.Texture: | ||
697 | return ThrottleOutPacketTypeFlags.Texture; | ||
698 | case ThrottleOutPacketType.Asset: | ||
699 | return ThrottleOutPacketTypeFlags.Asset; | ||
700 | case ThrottleOutPacketType.State: | ||
701 | return ThrottleOutPacketTypeFlags.State; | ||
702 | default: | ||
703 | return 0; | ||
704 | } | ||
705 | } | ||
706 | } | ||
707 | } | ||