From 0d2e6463d714bce8a6a628bd647c625feeeae8f6 Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Wed, 14 Oct 2009 11:43:31 -0700 Subject: * Minimized the number of times textures are pulled off the priority queue * OnQueueEmpty is still called async, but will not be called for a given category if the previous callback for that category is still running. This is the most balanced behavior I could find, and seems to work well * Added support for the old [ClientStack.LindenUDP] settings (including setting the receive buffer size) and added the new token bucket and global throttle settings * Added the AssetLoaderEnabled config variable to optionally disable loading assets from XML every startup. This gives a dramatic improvement in startup times for those who don't need the functionality every startup --- OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs | 25 +++++---- .../Region/ClientStack/LindenUDP/LLClientView.cs | 7 ++- .../Region/ClientStack/LindenUDP/LLImageManager.cs | 48 ++++++++-------- .../Region/ClientStack/LindenUDP/LLUDPClient.cs | 65 +++++++++++++++++----- .../Region/ClientStack/LindenUDP/LLUDPServer.cs | 12 +++- .../Region/ClientStack/LindenUDP/OpenSimUDPBase.cs | 15 ++++- .../Region/ClientStack/LindenUDP/ThrottleRates.cs | 36 +++++++----- 7 files changed, 141 insertions(+), 67 deletions(-) (limited to 'OpenSim/Region/ClientStack') diff --git a/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs b/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs index 5877779..9ded390 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs @@ -72,14 +72,20 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_imageManager = imageManager; } - public bool SendPackets(LLClientView client, int maxpack) + /// + /// Sends packets for this texture to a client until packetsToSend is + /// hit or the transfer completes + /// + /// Reference to the client that the packets are destined for + /// Maximum number of packets to send during this call + /// Number of packets sent during this call + /// True if the transfer completes at the current discard level, otherwise false + public bool SendPackets(LLClientView client, int packetsToSend, out int packetsSent) { - if (client == null) - return false; + packetsSent = 0; if (m_currentPacket <= m_stopPacket) { - int count = 0; bool sendMore = true; if (!m_sentInfo || (m_currentPacket == 0)) @@ -88,25 +94,22 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_sentInfo = true; ++m_currentPacket; - ++count; + ++packetsSent; } if (m_currentPacket < 2) { m_currentPacket = 2; } - while (sendMore && count < maxpack && m_currentPacket <= m_stopPacket) + while (sendMore && packetsSent < packetsToSend && m_currentPacket <= m_stopPacket) { sendMore = SendPacket(client); ++m_currentPacket; - ++count; + ++packetsSent; } - - if (m_currentPacket > m_stopPacket) - return true; } - return false; + return (m_currentPacket > m_stopPacket); } public void RunUpdate() diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs index 3b1a0bd..9afff5a 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs @@ -313,10 +313,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP protected int m_primFullUpdatesPerPacket = 14; protected int m_primTerseUpdateRate = 10; protected int m_primFullUpdateRate = 14; - protected int m_textureSendLimit = 20; - protected int m_textureDataLimit = 10; protected int m_avatarTerseUpdateRate = 50; protected int m_avatarTerseUpdatesPerPacket = 5; + /// Number of texture packets to put on the queue each time the + /// OnQueueEmpty event is triggered for the texture category + protected int m_textureSendLimit = 20; protected IAssetService m_assetService; #endregion Class Members @@ -3453,7 +3454,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP void ProcessTextureRequests() { if (m_imageManager != null) - m_imageManager.ProcessImageQueue(m_textureSendLimit, m_textureDataLimit); + m_imageManager.ProcessImageQueue(m_textureSendLimit); } void ProcessPrimFullUpdates(object sender, ElapsedEventArgs e) diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs b/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs index 8410ee9..9d36cff 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs @@ -162,32 +162,38 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } - public bool ProcessImageQueue(int count, int maxpack) + public bool ProcessImageQueue(int packetsToSend) { - J2KImage imagereq; - int numCollected = 0; - m_lastloopprocessed = DateTime.Now.Ticks; + int packetsSent = 0; - // This can happen during Close() - if (m_client == null) - return false; - - while ((imagereq = GetHighestPriorityImage()) != null) + while (packetsSent < packetsToSend) { - if (imagereq.IsDecoded == true) + J2KImage image = GetHighestPriorityImage(); + + // If null was returned, the texture priority queue is currently empty + if (image == null) + return false; + + if (image.IsDecoded) { - ++numCollected; + int sent; + bool imageDone = image.SendPackets(m_client, packetsToSend - packetsSent, out sent); - if (imagereq.SendPackets(m_client, maxpack)) - { - // Send complete. Destroy any knowledge of this transfer - RemoveImageFromQueue(imagereq); - } - } + packetsSent += sent; - if (numCollected == count) - break; + // If the send is complete, destroy any knowledge of this transfer + if (imageDone) + RemoveImageFromQueue(image); + } + else + { + // TODO: This is a limitation of how LLImageManager is currently + // written. Undecoded textures should not be going into the priority + // queue, because a high priority undecoded texture will clog up the + // pipeline for a client + return true; + } } return m_priorityQueue.Count > 0; @@ -199,10 +205,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void Close() { m_shuttingdown = true; - m_priorityQueue = null; - m_j2kDecodeModule = null; - m_assetCache = null; - m_client = null; } #region Priority Queue Helpers diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs index e7707a9..39472cb 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Generic; using System.Net; +using log4net; using OpenSim.Framework; using OpenMetaverse; @@ -59,6 +60,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// public sealed class LLUDPClient { + private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + // FIXME: Make this a config setting /// Percentage of the task throttle category that is allocated to avatar and prim /// state updates @@ -136,9 +139,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// A container that can hold one packet for each outbox, used to store /// dequeued packets that are being held for throttling private readonly OutgoingPacket[] nextPackets = new OutgoingPacket[THROTTLE_CATEGORY_COUNT]; - /// An optimization to store the length of dequeued packets being held - /// for throttling. This avoids expensive calls to Packet.Length - private readonly int[] nextPacketLengths = new int[THROTTLE_CATEGORY_COUNT]; + /// Flags to prevent queue empty callbacks from stacking up on + /// top of each other + private readonly bool[] onQueueEmptyRunning = new bool[THROTTLE_CATEGORY_COUNT]; /// A reference to the LLUDPServer that is managing this client private readonly LLUDPServer udpServer; @@ -163,7 +166,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) packetOutboxes[i] = new OpenSim.Framework.LocklessQueue(); - throttle = new TokenBucket(parentThrottle, 0, 0); + throttle = new TokenBucket(parentThrottle, rates.TotalLimit, rates.Total); throttleCategories = new TokenBucket[THROTTLE_CATEGORY_COUNT]; throttleCategories[(int)ThrottleOutPacketType.Resend] = new TokenBucket(throttle, rates.ResendLimit, rates.Resend); throttleCategories[(int)ThrottleOutPacketType.Land] = new TokenBucket(throttle, rates.LandLimit, rates.Land); @@ -401,10 +404,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP // This bucket was empty the last time we tried to send a packet, // leaving a dequeued packet still waiting to be sent out. Try to // send it again - if (bucket.RemoveTokens(nextPacketLengths[i])) + OutgoingPacket nextPacket = nextPackets[i]; + if (bucket.RemoveTokens(nextPacket.Buffer.DataLength)) { // Send the packet - udpServer.SendPacketFinal(nextPackets[i]); + udpServer.SendPacketFinal(nextPacket); nextPackets[i] = null; packetSent = true; } @@ -426,23 +430,21 @@ namespace OpenSim.Region.ClientStack.LindenUDP } else { - // Save the dequeued packet and the length calculation for - // the next iteration + // Save the dequeued packet for the next iteration nextPackets[i] = packet; - nextPacketLengths[i] = packet.Buffer.DataLength; } // If the queue is empty after this dequeue, fire the queue // empty callback now so it has a chance to fill before we // get back here if (queue.Count == 0) - FireQueueEmpty(i); + BeginFireQueueEmpty(i); } else { // No packets in this queue. Fire the queue empty callback // if it has not been called recently - FireQueueEmpty(i); + BeginFireQueueEmpty(i); } } } @@ -450,6 +452,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP return packetSent; } + /// + /// Called when an ACK packet is received and a round-trip time for a + /// packet is calculated. This is used to calculate the smoothed + /// round-trip time, round trip time variance, and finally the + /// retransmission timeout + /// + /// Round-trip time of a single packet and its + /// acknowledgement public void UpdateRoundTrip(float r) { const float ALPHA = 0.125f; @@ -475,11 +485,40 @@ namespace OpenSim.Region.ClientStack.LindenUDP // RTTVAR + " based on new RTT of " + r + "ms"); } - private void FireQueueEmpty(int queueIndex) + /// + /// Does an early check to see if this queue empty callback is already + /// running, then asynchronously firing the event + /// + /// Throttle category to fire the callback + /// for + private void BeginFireQueueEmpty(int throttleIndex) { + if (!onQueueEmptyRunning[throttleIndex]) + Util.FireAndForget(FireQueueEmpty, throttleIndex); + } + + /// + /// Checks to see if this queue empty callback is already running, + /// then firing the event + /// + /// Throttle category to fire the callback for, stored + /// as an object to match the WaitCallback delegate signature + private void FireQueueEmpty(object o) + { + int i = (int)o; + ThrottleOutPacketType type = (ThrottleOutPacketType)i; QueueEmpty callback = OnQueueEmpty; + if (callback != null) - Util.FireAndForget(delegate(object o) { callback((ThrottleOutPacketType)(int)o); }, queueIndex); + { + if (!onQueueEmptyRunning[i]) + { + onQueueEmptyRunning[i] = true; + try { callback(type); } + catch (Exception e) { m_log.Error("[LLUDPCLIENT]: OnQueueEmpty(" + type + ") threw an exception: " + e.Message, e); } + onQueueEmptyRunning[i] = false; + } + } } } } diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs index 57fee59..1cfde91 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs @@ -109,6 +109,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP private Location m_location; /// The measured resolution of Environment.TickCount private float m_tickCountResolution; + /// The size of the receive buffer for the UDP socket. This value + /// is passed up to the operating system and used in the system networking + /// stack. Use zero to leave this value as the default + private int m_recvBufferSize; /// The measured resolution of Environment.TickCount public float TickCountResolution { get { return m_tickCountResolution; } } @@ -135,6 +139,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_circuitManager = circuitManager; + IConfig config = configSource.Configs["ClientStack.LindenUDP"]; + if (config != null) + { + m_recvBufferSize = config.GetInt("client_socket_rcvbuf_size", 0); + } + // TODO: Config support for throttling the entire connection m_throttle = new TokenBucket(null, 0, 0); m_throttleRates = new ThrottleRates(configSource); @@ -145,7 +155,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (m_scene == null) throw new InvalidOperationException("[LLUDPSERVER]: Cannot LLUDPServer.Start() without an IScene reference"); - base.Start(); + base.Start(m_recvBufferSize); // Start the incoming packet processing thread Thread incomingThread = new Thread(IncomingPacketHandler); diff --git a/OpenSim/Region/ClientStack/LindenUDP/OpenSimUDPBase.cs b/OpenSim/Region/ClientStack/LindenUDP/OpenSimUDPBase.cs index fad2ea8..44a6ed6 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/OpenSimUDPBase.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/OpenSimUDPBase.cs @@ -73,6 +73,7 @@ namespace OpenMetaverse /// /// Local IP address to bind the server to /// Port to listening for incoming UDP packets on + /// public OpenSimUDPBase(IPAddress bindAddress, int port) { m_localBindAddress = bindAddress; @@ -82,25 +83,31 @@ namespace OpenMetaverse /// /// Start the UDP server /// + /// The size of the receive buffer for + /// the UDP socket. This value is passed up to the operating system + /// and used in the system networking stack. Use zero to leave this + /// value as the default /// This method will attempt to set the SIO_UDP_CONNRESET flag /// on the socket to get newer versions of Windows to behave in a sane /// manner (not throwing an exception when the remote side resets the /// connection). This call is ignored on Mono where the flag is not /// necessary - public void Start() + public void Start(int recvBufferSize) { if (m_shutdownFlag) { const int SIO_UDP_CONNRESET = -1744830452; IPEndPoint ipep = new IPEndPoint(m_localBindAddress, m_udpPort); + m_udpSocket = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + try { - // this udp socket flag is not supported under mono, + // This udp socket flag is not supported under mono, // so we'll catch the exception and continue m_udpSocket.IOControl(SIO_UDP_CONNRESET, new byte[] { 0 }, null); m_log.Debug("[UDPBASE]: SIO_UDP_CONNRESET flag set"); @@ -109,6 +116,10 @@ namespace OpenMetaverse { m_log.Debug("[UDPBASE]: SIO_UDP_CONNRESET flag not supported on this platform, ignoring"); } + + if (recvBufferSize != 0) + m_udpSocket.ReceiveBufferSize = recvBufferSize; + m_udpSocket.Bind(ipep); // we're not shutting down, we're starting up diff --git a/OpenSim/Region/ClientStack/LindenUDP/ThrottleRates.cs b/OpenSim/Region/ClientStack/LindenUDP/ThrottleRates.cs index 858a03c..adad4c3 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/ThrottleRates.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/ThrottleRates.cs @@ -51,6 +51,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP public int Texture; /// Drip rate for asset packets public int Asset; + /// Drip rate for the parent token bucket + public int Total; /// Maximum burst rate for resent packets public int ResendLimit; @@ -66,6 +68,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP public int TextureLimit; /// Maximum burst rate for asset packets public int AssetLimit; + /// Burst rate for the parent token bucket + public int TotalLimit; /// /// Default constructor @@ -77,21 +81,25 @@ namespace OpenSim.Region.ClientStack.LindenUDP { IConfig throttleConfig = config.Configs["ClientStack.LindenUDP"]; - Resend = throttleConfig.GetInt("ResendDefault", 12500); - Land = throttleConfig.GetInt("LandDefault", 500); - Wind = throttleConfig.GetInt("WindDefault", 500); - Cloud = throttleConfig.GetInt("CloudDefault", 500); - Task = throttleConfig.GetInt("TaskDefault", 500); - Texture = throttleConfig.GetInt("TextureDefault", 500); - Asset = throttleConfig.GetInt("AssetDefault", 500); + Resend = throttleConfig.GetInt("resend_default", 12500); + Land = throttleConfig.GetInt("land_default", 500); + Wind = throttleConfig.GetInt("wind_default", 500); + Cloud = throttleConfig.GetInt("cloud_default", 500); + Task = throttleConfig.GetInt("task_default", 500); + Texture = throttleConfig.GetInt("texture_default", 500); + Asset = throttleConfig.GetInt("asset_default", 500); - ResendLimit = throttleConfig.GetInt("ResendLimit", 18750); - LandLimit = throttleConfig.GetInt("LandLimit", 29750); - WindLimit = throttleConfig.GetInt("WindLimit", 18750); - CloudLimit = throttleConfig.GetInt("CloudLimit", 18750); - TaskLimit = throttleConfig.GetInt("TaskLimit", 55750); - TextureLimit = throttleConfig.GetInt("TextureLimit", 55750); - AssetLimit = throttleConfig.GetInt("AssetLimit", 27500); + Total = throttleConfig.GetInt("client_throttle_max_bps", 0); + + ResendLimit = throttleConfig.GetInt("resend_limit", 18750); + LandLimit = throttleConfig.GetInt("land_limit", 29750); + WindLimit = throttleConfig.GetInt("wind_limit", 18750); + CloudLimit = throttleConfig.GetInt("cloud_limit", 18750); + TaskLimit = throttleConfig.GetInt("task_limit", 55750); + TextureLimit = throttleConfig.GetInt("texture_limit", 55750); + AssetLimit = throttleConfig.GetInt("asset_limit", 27500); + + TotalLimit = throttleConfig.GetInt("client_throttle_max_bps", 0); } catch (Exception) { } } -- cgit v1.1