diff --git a/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs b/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs index 5877779d2b..9ded390767 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 3b1a0bdaa5..9afff5aee5 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 8410ee9bc0..9d36cffbf9 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 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; } - - if (numCollected == count) - break; } 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 e7707a9c58..39472cbc0a 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 57fee59b4a..1cfde9179a 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 fad2ea812d..44a6ed6083 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 858a03c097..adad4c3d83 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) { } } diff --git a/OpenSim/Services/AssetService/AssetService.cs b/OpenSim/Services/AssetService/AssetService.cs index ebfd47a52f..df33db2695 100644 --- a/OpenSim/Services/AssetService/AssetService.cs +++ b/OpenSim/Services/AssetService/AssetService.cs @@ -64,12 +64,17 @@ namespace OpenSim.Services.AssetService string loaderArgs = assetConfig.GetString("AssetLoaderArgs", String.Empty); - m_log.InfoFormat("[ASSET]: Loading default asset set from {0}", loaderArgs); - m_AssetLoader.ForEachDefaultXmlAsset(loaderArgs, - delegate(AssetBase a) - { - Store(a); - }); + bool assetLoaderEnabled = assetConfig.GetBoolean("AssetLoaderEnabled", true); + + if (assetLoaderEnabled) + { + m_log.InfoFormat("[ASSET]: Loading default asset set from {0}", loaderArgs); + m_AssetLoader.ForEachDefaultXmlAsset(loaderArgs, + delegate(AssetBase a) + { + Store(a); + }); + } m_log.Info("[ASSET CONNECTOR]: Local asset service enabled"); } diff --git a/bin/OpenSim.ini.example b/bin/OpenSim.ini.example index 5cb51b28d9..0ab6257694 100644 --- a/bin/OpenSim.ini.example +++ b/bin/OpenSim.ini.example @@ -341,25 +341,6 @@ [ClientStack.LindenUDP] - ; This is the multiplier applied to all client throttles for outgoing UDP network data - ; If it is set to 1, then we obey the throttle settings as given to us by the client. If it is set to 3, for example, then we - ; multiply that setting by 3 (e.g. if the client gives us a setting of 250 kilobits per second then we - ; will actually push down data at a maximum rate of 750 kilobits per second). - ; - ; In principle, setting a multiplier greater than 1 will allow data to be pushed down to a client much faster - ; than its UI allows the setting to go. This may be okay in some situations, such as standalone OpenSim - ; applications on a LAN. However, the greater the multipler, the higher the risk of packet drop, resulting - ; in symptoms such as missing terrain or objects. A much better solution is to change the client UI to allow - ; higher network bandwidth settings directly, though this isn't always possible. - ; - ; Currently this setting is 2 by default because we currently send much more texture data than is strictly - ; necessary. A setting of 1 could result in slow texture transfer. This will be fixed when the transfer - ; of textures at different levels of quality is improved. - ; - ; Pre r7113, this setting was not exposed but was effectively 8. You may want to try this if you encounter - ; unexpected difficulties - client_throttle_multiplier = 2; - ; the client socket receive buffer size determines how many ; incoming requests we can process; the default on .NET is 8192 ; which is about 2 4k-sized UDP datagrams. On mono this is @@ -374,12 +355,39 @@ ; by the system's settings for the maximum client receive buffer ; size (on linux systems you can set that with "sysctl -w ; net.core.rmem_max=X") - ; - ; client_socket_rcvbuf_size = 8388608 + ;client_socket_rcvbuf_size = 8388608 + + ; Maximum outbound bits per second for a single scene. This can be used to + ; throttle total outbound UDP traffic for a simulator. The default value is + ; 0, meaning no throttling at the scene level. The example given here is + ; 20 megabits + ;scene_throttle_max_bps = 20971520 - ; Maximum bits per second to send to any single client. This will override the user's viewer preference settings. - - ; client_throttle_max_bps = 1500000 + ; Maximum bits per second to send to any single client. This will override + ; the user's viewer preference settings. The default value is 0, meaning no + ; aggregate throttling on clients (only per-category throttling). The + ; example given here is 1.5 megabits + ;client_throttle_max_bps = 1572864 + + ; Per-client bits per second rates for the various throttle categories. + ; These are default values that will be overriden by clients + ;resend_default = 12500 + ;land_default = 500 + ;wind_default = 500 + ;cloud_default = 50 + ;task_default = 500 + ;texture_default = 500 + ;asset_default = 500 + + ; Per-client maximum burst rates in bits per second for the various throttle + ; categories. These are default values that will be overriden by clients + ;resend_limit = 18750 + ;land_limit = 29750 + ;wind_limit = 18750 + ;cloud_limit = 18750 + ;task_limit = 55750 + ;texture_limit = 55750 + ;asset_limit = 27500 [Chat] ; Controls whether the chat module is enabled. Default is true. @@ -1381,6 +1389,10 @@ [AssetService] DefaultAssetLoader = "OpenSim.Framework.AssetLoader.Filesystem.dll" AssetLoaderArgs = "assets/AssetSets.xml" + + ; Disable this to prevent the default asset set from being inserted into the + ; asset store each time the region starts + AssetLoaderEnabled = true [GridService] ;; default standalone, overridable in StandaloneCommon.ini