From e7c877407f2a72a9519eb53debca5aeef20cded9 Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Tue, 6 Oct 2009 02:38:00 -0700 Subject: * Continued work on the new LLUDP implementation. Appears to be functioning, although not everything is reimplemented yet * Replaced logic in ThreadTracker with a call to System.Diagnostics that does the same thing * Added Util.StringToBytes256() and Util.StringToBytes1024() to clamp output at byte[256] and byte[1024], respectively * Fixed formatting for a MySQLAssetData error logging line --- OpenSim/Data/MySQL/MySQLAssetData.cs | 6 +- OpenSim/Framework/Parallel.cs | 207 +++++ OpenSim/Framework/ThreadTracker.cs | 127 +-- OpenSim/Framework/Util.cs | 36 + .../Region/ClientStack/LindenUDP/IncomingPacket.cs | 42 + .../LindenUDP/IncomingPacketHistoryCollection.cs | 73 ++ OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs | 33 +- OpenSim/Region/ClientStack/LindenUDP/KillPacket.cs | 71 -- .../Region/ClientStack/LindenUDP/LLClientView.cs | 284 +++---- .../ClientStack/LindenUDP/LLPacketHandler.cs | 875 --------------------- .../Region/ClientStack/LindenUDP/LLPacketQueue.cs | 742 ----------------- .../Region/ClientStack/LindenUDP/LLPacketServer.cs | 206 ----- OpenSim/Region/ClientStack/LindenUDP/LLQueItem.cs | 49 -- .../Region/ClientStack/LindenUDP/LLUDPClient.cs | 370 +++++++++ .../ClientStack/LindenUDP/LLUDPClientCollection.cs | 185 +++++ .../Region/ClientStack/LindenUDP/LLUDPServer.cs | 138 +++- OpenSim/Region/ClientStack/LindenUDP/LLUtil.cs | 56 -- .../Region/ClientStack/LindenUDP/OutgoingPacket.cs | 56 ++ .../Region/ClientStack/LindenUDP/ThrottleRates.cs | 76 ++ .../LindenUDP/UnackedPacketCollection.cs | 114 +++ 20 files changed, 1391 insertions(+), 2355 deletions(-) create mode 100644 OpenSim/Framework/Parallel.cs create mode 100644 OpenSim/Region/ClientStack/LindenUDP/IncomingPacket.cs create mode 100644 OpenSim/Region/ClientStack/LindenUDP/IncomingPacketHistoryCollection.cs delete mode 100644 OpenSim/Region/ClientStack/LindenUDP/KillPacket.cs delete mode 100644 OpenSim/Region/ClientStack/LindenUDP/LLPacketHandler.cs delete mode 100644 OpenSim/Region/ClientStack/LindenUDP/LLPacketQueue.cs delete mode 100644 OpenSim/Region/ClientStack/LindenUDP/LLPacketServer.cs delete mode 100644 OpenSim/Region/ClientStack/LindenUDP/LLQueItem.cs create mode 100644 OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs create mode 100644 OpenSim/Region/ClientStack/LindenUDP/LLUDPClientCollection.cs delete mode 100644 OpenSim/Region/ClientStack/LindenUDP/LLUtil.cs create mode 100644 OpenSim/Region/ClientStack/LindenUDP/OutgoingPacket.cs create mode 100644 OpenSim/Region/ClientStack/LindenUDP/ThrottleRates.cs create mode 100644 OpenSim/Region/ClientStack/LindenUDP/UnackedPacketCollection.cs (limited to 'OpenSim') diff --git a/OpenSim/Data/MySQL/MySQLAssetData.cs b/OpenSim/Data/MySQL/MySQLAssetData.cs index 8f97440..259e186 100644 --- a/OpenSim/Data/MySQL/MySQLAssetData.cs +++ b/OpenSim/Data/MySQL/MySQLAssetData.cs @@ -239,10 +239,8 @@ namespace OpenSim.Data.MySQL } catch (Exception e) { - m_log.ErrorFormat( - "[ASSETS DB]: " + - "MySql failure creating asset {0} with name {1}" + Environment.NewLine + e.ToString() - + Environment.NewLine + "Attempting reconnection", asset.FullID, asset.Name); + m_log.ErrorFormat("[ASSET DB]: MySQL failure creating asset {0} with name \"{1}\". Attempting reconnect. Error: {2}", + asset.FullID, asset.Name, e.Message); _dbConnection.Reconnect(); } } diff --git a/OpenSim/Framework/Parallel.cs b/OpenSim/Framework/Parallel.cs new file mode 100644 index 0000000..74537ba --- /dev/null +++ b/OpenSim/Framework/Parallel.cs @@ -0,0 +1,207 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Threading; + +namespace OpenSim.Framework +{ + /// + /// Provides helper methods for parallelizing loops + /// + public static class Parallel + { + private static readonly int processorCount = System.Environment.ProcessorCount; + + /// + /// Executes a for loop in which iterations may run in parallel + /// + /// The loop will be started at this index + /// The loop will be terminated before this index is reached + /// Method body to run for each iteration of the loop + public static void For(int fromInclusive, int toExclusive, Action body) + { + For(processorCount, fromInclusive, toExclusive, body); + } + + /// + /// Executes a for loop in which iterations may run in parallel + /// + /// The number of concurrent execution threads to run + /// The loop will be started at this index + /// The loop will be terminated before this index is reached + /// Method body to run for each iteration of the loop + public static void For(int threadCount, int fromInclusive, int toExclusive, Action body) + { + int counter = threadCount; + AutoResetEvent threadFinishEvent = new AutoResetEvent(false); + Exception exception = null; + + --fromInclusive; + + for (int i = 0; i < threadCount; i++) + { + ThreadPool.QueueUserWorkItem( + delegate(object o) + { + int threadIndex = (int)o; + + while (exception == null) + { + int currentIndex = Interlocked.Increment(ref fromInclusive); + + if (currentIndex >= toExclusive) + break; + + try { body(currentIndex); } + catch (Exception ex) { exception = ex; break; } + } + + if (Interlocked.Decrement(ref counter) == 0) + threadFinishEvent.Set(); + }, i + ); + } + + threadFinishEvent.WaitOne(); + + if (exception != null) + throw new Exception(exception.Message, exception); + } + + /// + /// Executes a foreach loop in which iterations may run in parallel + /// + /// Object type that the collection wraps + /// An enumerable collection to iterate over + /// Method body to run for each object in the collection + public static void ForEach(IEnumerable enumerable, Action body) + { + ForEach(processorCount, enumerable, body); + } + + /// + /// Executes a foreach loop in which iterations may run in parallel + /// + /// Object type that the collection wraps + /// The number of concurrent execution threads to run + /// An enumerable collection to iterate over + /// Method body to run for each object in the collection + public static void ForEach(int threadCount, IEnumerable enumerable, Action body) + { + int counter = threadCount; + AutoResetEvent threadFinishEvent = new AutoResetEvent(false); + IEnumerator enumerator = enumerable.GetEnumerator(); + Exception exception = null; + + for (int i = 0; i < threadCount; i++) + { + ThreadPool.QueueUserWorkItem( + delegate(object o) + { + int threadIndex = (int)o; + + while (exception == null) + { + T entry; + + lock (enumerator) + { + if (!enumerator.MoveNext()) + break; + entry = (T)enumerator.Current; // Explicit typecast for Mono's sake + } + + try { body(entry); } + catch (Exception ex) { exception = ex; break; } + } + + if (Interlocked.Decrement(ref counter) == 0) + threadFinishEvent.Set(); + }, i + ); + } + + threadFinishEvent.WaitOne(); + + if (exception != null) + throw new Exception(exception.Message, exception); + } + + /// + /// Executes a series of tasks in parallel + /// + /// A series of method bodies to execute + public static void Invoke(params Action[] actions) + { + Invoke(processorCount, actions); + } + + /// + /// Executes a series of tasks in parallel + /// + /// The number of concurrent execution threads to run + /// A series of method bodies to execute + public static void Invoke(int threadCount, params Action[] actions) + { + int counter = threadCount; + AutoResetEvent threadFinishEvent = new AutoResetEvent(false); + int index = -1; + Exception exception = null; + + for (int i = 0; i < threadCount; i++) + { + ThreadPool.QueueUserWorkItem( + delegate(object o) + { + int threadIndex = (int)o; + + while (exception == null) + { + int currentIndex = Interlocked.Increment(ref index); + + if (currentIndex >= actions.Length) + break; + + try { actions[currentIndex](); } + catch (Exception ex) { exception = ex; break; } + } + + if (Interlocked.Decrement(ref counter) == 0) + threadFinishEvent.Set(); + }, i + ); + } + + threadFinishEvent.WaitOne(); + + if (exception != null) + throw new Exception(exception.Message, exception); + } + } +} diff --git a/OpenSim/Framework/ThreadTracker.cs b/OpenSim/Framework/ThreadTracker.cs index d3a239d..b68d9b3 100644 --- a/OpenSim/Framework/ThreadTracker.cs +++ b/OpenSim/Framework/ThreadTracker.cs @@ -26,138 +26,21 @@ */ using System; -using System.Collections; using System.Collections.Generic; using System.Reflection; -using System.Threading; +using System.Diagnostics; using log4net; namespace OpenSim.Framework { public static class ThreadTracker { - private static readonly ILog m_log - = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - private static readonly long ThreadTimeout = 30 * 10000000; - public static List m_Threads; - public static Thread ThreadTrackerThread; + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - static ThreadTracker() + public static ProcessThreadCollection GetThreads() { -#if DEBUG - m_Threads = new List(); - ThreadTrackerThread = new Thread(ThreadTrackerThreadLoop); - ThreadTrackerThread.Name = "ThreadTrackerThread"; - ThreadTrackerThread.IsBackground = true; - ThreadTrackerThread.Priority = ThreadPriority.BelowNormal; - ThreadTrackerThread.Start(); - Add(ThreadTrackerThread); -#endif + Process thisProc = Process.GetCurrentProcess(); + return thisProc.Threads; } - - private static void ThreadTrackerThreadLoop() - { - try - { - while (true) - { - Thread.Sleep(5000); - CleanUp(); - } - } - catch (Exception e) - { - m_log.ErrorFormat( - "[THREAD TRACKER]: Thread tracker cleanup thread terminating with exception. Please report this error. Exception is {0}", - e); - } - } - - public static void Add(Thread thread) - { -#if DEBUG - if (thread != null) - { - lock (m_Threads) - { - ThreadTrackerItem tti = new ThreadTrackerItem(); - tti.Thread = thread; - tti.LastSeenActive = DateTime.Now.Ticks; - m_Threads.Add(tti); - } - } -#endif - } - - public static void Remove(Thread thread) - { -#if DEBUG - lock (m_Threads) - { - foreach (ThreadTrackerItem tti in new ArrayList(m_Threads)) - { - if (tti.Thread == thread) - m_Threads.Remove(tti); - } - } -#endif - } - - public static void CleanUp() - { - lock (m_Threads) - { - foreach (ThreadTrackerItem tti in new ArrayList(m_Threads)) - { - try - { - - - if (tti.Thread.IsAlive) - { - // Its active - tti.LastSeenActive = DateTime.Now.Ticks; - } - else - { - // Its not active -- if its expired then remove it - if (tti.LastSeenActive + ThreadTimeout < DateTime.Now.Ticks) - m_Threads.Remove(tti); - } - } - catch (NullReferenceException) - { - m_Threads.Remove(tti); - } - } - } - } - - public static List GetThreads() - { - if (m_Threads == null) - return null; - - List threads = new List(); - lock (m_Threads) - { - foreach (ThreadTrackerItem tti in new ArrayList(m_Threads)) - { - threads.Add(tti.Thread); - } - } - return threads; - } - - #region Nested type: ThreadTrackerItem - - public class ThreadTrackerItem - { - public long LastSeenActive; - public Thread Thread; - } - - #endregion } } diff --git a/OpenSim/Framework/Util.cs b/OpenSim/Framework/Util.cs index 0851d26..189fa38 100644 --- a/OpenSim/Framework/Util.cs +++ b/OpenSim/Framework/Util.cs @@ -1231,6 +1231,42 @@ namespace OpenSim.Framework return (ipaddr1 != null) ? "http://" + ipaddr1.ToString() + ":" + port1 : uri; } + public static byte[] StringToBytes256(string str) + { + if (String.IsNullOrEmpty(str)) { return Utils.EmptyBytes; } + if (str.Length > 254) str = str.Remove(254); + if (!str.EndsWith("\0")) { str += "\0"; } + + // Because this is UTF-8 encoding and not ASCII, it's possible we + // might have gotten an oversized array even after the string trim + byte[] data = UTF8.GetBytes(str); + if (data.Length > 256) + { + Array.Resize(ref data, 256); + data[255] = 0; + } + + return data; + } + + public static byte[] StringToBytes1024(string str) + { + if (String.IsNullOrEmpty(str)) { return Utils.EmptyBytes; } + if (str.Length > 1023) str = str.Remove(1023); + if (!str.EndsWith("\0")) { str += "\0"; } + + // Because this is UTF-8 encoding and not ASCII, it's possible we + // might have gotten an oversized array even after the string trim + byte[] data = UTF8.GetBytes(str); + if (data.Length > 1024) + { + Array.Resize(ref data, 1024); + data[1023] = 0; + } + + return data; + } + #region FireAndForget Threading Pattern public static void FireAndForget(System.Threading.WaitCallback callback) diff --git a/OpenSim/Region/ClientStack/LindenUDP/IncomingPacket.cs b/OpenSim/Region/ClientStack/LindenUDP/IncomingPacket.cs new file mode 100644 index 0000000..dc0d62a --- /dev/null +++ b/OpenSim/Region/ClientStack/LindenUDP/IncomingPacket.cs @@ -0,0 +1,42 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenSim.Framework; +using OpenMetaverse; +using OpenMetaverse.Packets; + +namespace OpenSim.Region.ClientStack.LindenUDP +{ + public struct IncomingPacket + { + /// Client this packet came from + public LLUDPClient Client; + /// Packet data that has been received + public Packet Packet; + } +} diff --git a/OpenSim/Region/ClientStack/LindenUDP/IncomingPacketHistoryCollection.cs b/OpenSim/Region/ClientStack/LindenUDP/IncomingPacketHistoryCollection.cs new file mode 100644 index 0000000..1f73a1d --- /dev/null +++ b/OpenSim/Region/ClientStack/LindenUDP/IncomingPacketHistoryCollection.cs @@ -0,0 +1,73 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; + +namespace OpenSim.Region.ClientStack.LindenUDP +{ + /// + /// A circular buffer and hashset for tracking incoming packet sequence + /// numbers + /// + public sealed class IncomingPacketHistoryCollection + { + private readonly uint[] m_items; + private HashSet m_hashSet; + private int m_first; + private int m_next; + private int m_capacity; + + public IncomingPacketHistoryCollection(int capacity) + { + this.m_capacity = capacity; + m_items = new uint[capacity]; + m_hashSet = new HashSet(); + } + + public bool TryEnqueue(uint ack) + { + lock (m_hashSet) + { + if (m_hashSet.Add(ack)) + { + m_items[m_next] = ack; + m_next = (m_next + 1) % m_capacity; + if (m_next == m_first) + { + m_hashSet.Remove(m_items[m_first]); + m_first = (m_first + 1) % m_capacity; + } + + return true; + } + } + + return false; + } + } +} diff --git a/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs b/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs index 000f455..2f1face 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs @@ -76,27 +76,27 @@ namespace OpenSim.Region.ClientStack.LindenUDP { if (m_currentPacket <= m_stopPacket) { - bool SendMore = true; + int count = 0; + bool sendMore = true; + if (!m_sentInfo || (m_currentPacket == 0)) { - if (SendFirstPacket(client)) - { - SendMore = false; - } + sendMore = !SendFirstPacket(client); + m_sentInfo = true; - m_currentPacket++; + ++m_currentPacket; + ++count; } if (m_currentPacket < 2) { m_currentPacket = 2; } - - int count = 0; - while (SendMore && count < maxpack && m_currentPacket <= m_stopPacket) + + while (sendMore && count < maxpack && m_currentPacket <= m_stopPacket) { - count++; - SendMore = SendPacket(client); - m_currentPacket++; + sendMore = SendPacket(client); + ++m_currentPacket; + ++count; } if (m_currentPacket > m_stopPacket) @@ -196,15 +196,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_currentPacket = StartPacket; } - - if (m_imageManager != null && m_imageManager.Client != null) - { - if (m_imageManager.Client.IsThrottleEmpty(ThrottleOutPacketType.Texture)) - { - //m_log.Debug("No textures queued, sending one packet to kickstart it"); - SendPacket(m_imageManager.Client); - } - } } } } diff --git a/OpenSim/Region/ClientStack/LindenUDP/KillPacket.cs b/OpenSim/Region/ClientStack/LindenUDP/KillPacket.cs deleted file mode 100644 index a80c1f0..0000000 --- a/OpenSim/Region/ClientStack/LindenUDP/KillPacket.cs +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using OpenMetaverse.Packets; - -namespace OpenSim.Region.ClientStack.LindenUDP -{ - /// - /// When packetqueue dequeues this packet in the outgoing stream, it thread aborts - /// Ensures that the thread abort happens from within the client thread - /// regardless of where the close method is called - /// - class KillPacket : Packet - { - public override int Length - { - get { return 0; } - } - - public override void FromBytes(Header header, byte[] bytes, ref int i, ref int packetEnd) - { - } - - public override void FromBytes(byte[] bytes, ref int i, ref int packetEnd, byte[] zeroBuffer) - { - } - - public override byte[] ToBytes() - { - return new byte[0]; - } - - public override byte[][] ToBytesMultiple() - { - return new byte[][] { new byte[0] }; - } - - public KillPacket() - { - Type = PacketType.UseCircuitCode; - Header = new Header(); - Header.Frequency = OpenMetaverse.PacketFrequency.Low; - Header.ID = 65531; - Header.Reliable = true; - } - } -} diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs index 31cd53f..767020f 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs @@ -70,28 +70,23 @@ namespace OpenSim.Region.ClientStack.LindenUDP private readonly LLUDPServer m_udpServer; private readonly LLUDPClient m_udpClient; private readonly UUID m_sessionId; - private readonly UUID m_secureSessionId = UUID.Zero; + private readonly UUID m_secureSessionId; private readonly UUID m_agentId; private readonly uint m_circuitCode; - private readonly byte[] m_channelVersion = Utils.StringToBytes("OpenSimulator Server"); // Dummy value needed by libSL + private readonly byte[] m_channelVersion = Utils.EmptyBytes; private readonly Dictionary m_defaultAnimations = new Dictionary(); private readonly IGroupsModule m_GroupsModule; - private int m_debugPacketLevel; private int m_cachedTextureSerial; - private Timer m_clientPingTimer; private Timer m_avatarTerseUpdateTimer; private List m_avatarTerseUpdates = new List(); private Timer m_primTerseUpdateTimer; private List m_primTerseUpdates = new List(); private Timer m_primFullUpdateTimer; private List m_primFullUpdates = new List(); - private bool m_clientBlocked; - private int m_probesWithNoIngressPackets; private int m_moneyBalance; private int m_animationSequenceNumber = 1; private bool m_SendLogoutPacketWhenClosing = true; - private int m_inPacketsChecked; private AgentUpdateArgs lastarg; private bool m_IsActive = true; @@ -170,58 +165,72 @@ namespace OpenSim.Region.ClientStack.LindenUDP RegisterInterface(this); RegisterInterface(this); RegisterInterface(this); - m_GroupsModule = scene.RequestModuleInterface(); - - m_moneyBalance = 1000; - - m_channelVersion = Utils.StringToBytes(scene.GetSimulatorVersion()); - + InitDefaultAnimations(); m_scene = scene; - //m_assetCache = assetCache; - m_assetService = m_scene.RequestModuleInterface(); - - m_udpServer = udpServer; - m_udpClient = udpClient; - + m_GroupsModule = scene.RequestModuleInterface(); + m_imageManager = new LLImageManager(this, m_assetService, Scene.RequestModuleInterface()); + m_channelVersion = Utils.StringToBytes(scene.GetSimulatorVersion()); m_agentId = agentId; m_sessionId = sessionId; + m_secureSessionId = sessionInfo.LoginInfo.SecureSession; m_circuitCode = circuitCode; - m_userEndPoint = remoteEP; - m_firstName = sessionInfo.LoginInfo.First; m_lastName = sessionInfo.LoginInfo.Last; m_startpos = sessionInfo.LoginInfo.StartPos; + m_moneyBalance = 1000; - if (sessionInfo.LoginInfo.SecureSession != UUID.Zero) - { - m_secureSessionId = sessionInfo.LoginInfo.SecureSession; - } - - // While working on this, the BlockingQueue had me fooled for a bit. - // The Blocking queue causes the thread to stop until there's something - // in it to process. It's an on-purpose threadlock though because - // without it, the clientloop will suck up all sim resources. - - //m_PacketHandler = new LLPacketHandler(this, m_networkServer, userSettings); - //m_PacketHandler.SynchronizeClient = SynchronizeClient; - //m_PacketHandler.OnPacketStats += PopulateStats; - //m_PacketHandler.OnQueueEmpty += HandleQueueEmpty; + m_udpServer = udpServer; + m_udpClient = udpClient; + m_udpClient.OnQueueEmpty += HandleQueueEmpty; + // FIXME: Implement this + //m_udpClient.OnPacketStats += PopulateStats; RegisterLocalPacketHandlers(); - m_imageManager = new LLImageManager(this, m_assetService, Scene.RequestModuleInterface()); } - public void SetDebugPacketLevel(int newDebugPacketLevel) + public void SetDebugPacketLevel(int newDebug) { - m_debugPacketLevel = newDebugPacketLevel; } #region Client Methods + /// + /// Close down the client view. This *must* be the last method called, since the last # + /// statement of CloseCleanup() aborts the thread. + /// + /// + public void Close(bool shutdownCircuit) + { + m_log.DebugFormat( + "[CLIENT]: Close has been called with shutdownCircuit = {0} for {1} attached to scene {2}", + shutdownCircuit, Name, m_scene.RegionInfo.RegionName); + + if (m_imageManager != null) + m_imageManager.Close(); + + if (m_udpServer != null) + m_udpServer.Flush(); + + // raise an event on the packet server to Shutdown the circuit + // Now, if we raise the event then the packet server will call this method itself, so don't try cleanup + // here otherwise we'll end up calling it twice. + // FIXME: In truth, I might be wrong but this whole business of calling this method twice (with different args) looks + // horribly tangly. Hopefully it should be possible to greatly simplify it. + if (shutdownCircuit) + { + if (OnConnectionClosed != null) + OnConnectionClosed(this); + } + else + { + CloseCleanup(shutdownCircuit); + } + } + private void CloseCleanup(bool shutdownCircuit) { m_scene.RemoveClient(AgentId); @@ -236,9 +245,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP Thread.Sleep(2000); // Shut down timers. Thread Context of this method is murky. Lock all timers - if (m_clientPingTimer.Enabled) - lock (m_clientPingTimer) - m_clientPingTimer.Stop(); if (m_avatarTerseUpdateTimer.Enabled) lock (m_avatarTerseUpdateTimer) m_avatarTerseUpdateTimer.Stop(); @@ -271,43 +277,21 @@ namespace OpenSim.Region.ClientStack.LindenUDP // of the client thread regardless of where Close() is called. KillEndDone(); } - - Terminate(); - } - /// - /// Close down the client view. This *must* be the last method called, since the last # - /// statement of CloseCleanup() aborts the thread. - /// - /// - public void Close(bool shutdownCircuit) - { - m_clientPingTimer.Enabled = false; + IsActive = false; - m_log.DebugFormat( - "[CLIENT]: Close has been called with shutdownCircuit = {0} for {1} attached to scene {2}", - shutdownCircuit, Name, m_scene.RegionInfo.RegionName); + m_avatarTerseUpdateTimer.Close(); + m_primTerseUpdateTimer.Close(); + m_primFullUpdateTimer.Close(); - if (m_imageManager != null) - m_imageManager.Close(); + //m_udpServer.OnPacketStats -= PopulateStats; + m_udpClient.Shutdown(); - if (m_udpServer != null) - m_udpServer.Flush(); + // wait for thread stoped + // m_clientThread.Join(); - // raise an event on the packet server to Shutdown the circuit - // Now, if we raise the event then the packet server will call this method itself, so don't try cleanup - // here otherwise we'll end up calling it twice. - // FIXME: In truth, I might be wrong but this whole business of calling this method twice (with different args) looks - // horribly tangly. Hopefully it should be possible to greatly simplify it. - if (shutdownCircuit) - { - if (OnConnectionClosed != null) - OnConnectionClosed(this); - } - else - { - CloseCleanup(shutdownCircuit); - } + // delete circuit code + //m_networkServer.CloseClient(this); } public void Kick(string message) @@ -329,10 +313,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void Stop() { // Shut down timers. Thread Context is Murky, lock all timers! - if (m_clientPingTimer.Enabled) - lock (m_clientPingTimer) - m_clientPingTimer.Stop(); - if (m_avatarTerseUpdateTimer.Enabled) lock (m_avatarTerseUpdateTimer) m_avatarTerseUpdateTimer.Stop(); @@ -346,25 +326,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_primFullUpdateTimer.Stop(); } - private void Terminate() - { - IsActive = false; - - m_clientPingTimer.Close(); - m_avatarTerseUpdateTimer.Close(); - m_primTerseUpdateTimer.Close(); - m_primFullUpdateTimer.Close(); - - //m_udpServer.OnPacketStats -= PopulateStats; - m_udpClient.Shutdown(); - - // wait for thread stoped - // m_clientThread.Join(); - - // delete circuit code - //m_networkServer.CloseClient(this); - } - #endregion Client Methods #region Packet Handling @@ -452,7 +413,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP return result; } - protected void DebugPacket(string direction, Packet packet) + /*protected void DebugPacket(string direction, Packet packet) { string info; @@ -478,7 +439,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP } Console.WriteLine(m_circuitCode + ":" + direction + ": " + info); - } + }*/ #endregion Packet Handling @@ -490,8 +451,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// protected virtual void InitNewClient() { - //this.UploadAssets = new AgentAssetUpload(this, m_assetCache, m_inventoryCache); - m_avatarTerseUpdateTimer = new Timer(m_avatarTerseUpdateRate); m_avatarTerseUpdateTimer.Elapsed += new ElapsedEventHandler(ProcessAvatarTerseUpdates); m_avatarTerseUpdateTimer.AutoReset = false; @@ -511,11 +470,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP public virtual void Start() { - m_clientThread = new Thread(RunUserSession); - m_clientThread.Name = "ClientThread"; - m_clientThread.IsBackground = true; - m_clientThread.Start(); - ThreadTracker.Add(m_clientThread); + // This sets up all the timers + InitNewClient(); } /// @@ -523,14 +479,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// protected void RunUserSession() { - //tell this thread we are using the culture set up for the sim (currently hardcoded to en_US) - //otherwise it will override this and use the system default - Culture.SetCurrentCulture(); - try { - // This sets up all the timers - InitNewClient(); + } catch (Exception e) { @@ -1373,8 +1324,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void SendStartPingCheck(byte seq) { StartPingCheckPacket pc = (StartPingCheckPacket)PacketPool.Instance.GetPacket(PacketType.StartPingCheck); - pc.PingID.PingID = seq; pc.Header.Reliable = false; + + OutgoingPacket oldestPacket = m_udpClient.NeedAcks.GetOldest(); + + pc.PingID.PingID = seq; + pc.PingID.OldestUnacked = (oldestPacket != null) ? oldestPacket.SequenceNumber : 0; + OutPacket(pc, ThrottleOutPacketType.Unknown); } @@ -1450,12 +1406,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP descend.ItemData[i].AssetID = item.AssetID; descend.ItemData[i].CreatorID = item.CreatorIdAsUuid; descend.ItemData[i].BaseMask = item.BasePermissions; - descend.ItemData[i].Description = LLUtil.StringToPacketBytes(item.Description); + descend.ItemData[i].Description = Util.StringToBytes256(item.Description); descend.ItemData[i].EveryoneMask = item.EveryOnePermissions; descend.ItemData[i].OwnerMask = item.CurrentPermissions; descend.ItemData[i].FolderID = item.Folder; descend.ItemData[i].InvType = (sbyte)item.InvType; - descend.ItemData[i].Name = LLUtil.StringToPacketBytes(item.Name); + descend.ItemData[i].Name = Util.StringToBytes256(item.Name); descend.ItemData[i].NextOwnerMask = item.NextPermissions; descend.ItemData[i].OwnerID = item.Owner; descend.ItemData[i].Type = (sbyte)item.AssetType; @@ -1536,7 +1492,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP { descend.FolderData[i] = new InventoryDescendentsPacket.FolderDataBlock(); descend.FolderData[i].FolderID = folder.ID; - descend.FolderData[i].Name = LLUtil.StringToPacketBytes(folder.Name); + descend.FolderData[i].Name = Util.StringToBytes256(folder.Name); descend.FolderData[i].ParentID = folder.ParentID; descend.FolderData[i].Type = (sbyte)folder.Type; @@ -1651,11 +1607,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP inventoryReply.InventoryData[0].BaseMask = item.BasePermissions; inventoryReply.InventoryData[0].CreationDate = item.CreationDate; - inventoryReply.InventoryData[0].Description = LLUtil.StringToPacketBytes(item.Description); + inventoryReply.InventoryData[0].Description = Util.StringToBytes256(item.Description); inventoryReply.InventoryData[0].EveryoneMask = item.EveryOnePermissions; inventoryReply.InventoryData[0].FolderID = item.Folder; inventoryReply.InventoryData[0].InvType = (sbyte)item.InvType; - inventoryReply.InventoryData[0].Name = LLUtil.StringToPacketBytes(item.Name); + inventoryReply.InventoryData[0].Name = Util.StringToBytes256(item.Name); inventoryReply.InventoryData[0].NextOwnerMask = item.NextPermissions; inventoryReply.InventoryData[0].OwnerID = item.Owner; inventoryReply.InventoryData[0].OwnerMask = item.CurrentPermissions; @@ -1780,7 +1736,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP folderBlock.FolderID = folder.ID; folderBlock.ParentID = folder.ParentID; folderBlock.Type = -1; - folderBlock.Name = LLUtil.StringToPacketBytes(folder.Name); + folderBlock.Name = Util.StringToBytes256(folder.Name); return folderBlock; } @@ -1798,11 +1754,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP itemBlock.AssetID = item.AssetID; itemBlock.CreatorID = item.CreatorIdAsUuid; itemBlock.BaseMask = item.BasePermissions; - itemBlock.Description = LLUtil.StringToPacketBytes(item.Description); + itemBlock.Description = Util.StringToBytes256(item.Description); itemBlock.EveryoneMask = item.EveryOnePermissions; itemBlock.FolderID = item.Folder; itemBlock.InvType = (sbyte)item.InvType; - itemBlock.Name = LLUtil.StringToPacketBytes(item.Name); + itemBlock.Name = Util.StringToBytes256(item.Name); itemBlock.NextOwnerMask = item.NextPermissions; itemBlock.OwnerID = item.Owner; itemBlock.OwnerMask = item.CurrentPermissions; @@ -1862,11 +1818,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP bulkUpdate.ItemData[0].CreatorID = item.CreatorIdAsUuid; bulkUpdate.ItemData[0].BaseMask = item.BasePermissions; bulkUpdate.ItemData[0].CreationDate = item.CreationDate; - bulkUpdate.ItemData[0].Description = LLUtil.StringToPacketBytes(item.Description); + bulkUpdate.ItemData[0].Description = Util.StringToBytes256(item.Description); bulkUpdate.ItemData[0].EveryoneMask = item.EveryOnePermissions; bulkUpdate.ItemData[0].FolderID = item.Folder; bulkUpdate.ItemData[0].InvType = (sbyte)item.InvType; - bulkUpdate.ItemData[0].Name = LLUtil.StringToPacketBytes(item.Name); + bulkUpdate.ItemData[0].Name = Util.StringToBytes256(item.Name); bulkUpdate.ItemData[0].NextOwnerMask = item.NextPermissions; bulkUpdate.ItemData[0].OwnerID = item.Owner; bulkUpdate.ItemData[0].OwnerMask = item.CurrentPermissions; @@ -1909,11 +1865,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP InventoryReply.InventoryData[0].AssetID = Item.AssetID; InventoryReply.InventoryData[0].CreatorID = Item.CreatorIdAsUuid; InventoryReply.InventoryData[0].BaseMask = Item.BasePermissions; - InventoryReply.InventoryData[0].Description = LLUtil.StringToPacketBytes(Item.Description); + InventoryReply.InventoryData[0].Description = Util.StringToBytes256(Item.Description); InventoryReply.InventoryData[0].EveryoneMask = Item.EveryOnePermissions; InventoryReply.InventoryData[0].FolderID = Item.Folder; InventoryReply.InventoryData[0].InvType = (sbyte)Item.InvType; - InventoryReply.InventoryData[0].Name = LLUtil.StringToPacketBytes(Item.Name); + InventoryReply.InventoryData[0].Name = Util.StringToBytes256(Item.Name); InventoryReply.InventoryData[0].NextOwnerMask = Item.NextPermissions; InventoryReply.InventoryData[0].OwnerID = Item.Owner; InventoryReply.InventoryData[0].OwnerMask = Item.CurrentPermissions; @@ -2080,7 +2036,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// /// /// - protected AgentAlertMessagePacket BuildAgentAlertPacket(string message, bool modal) + public AgentAlertMessagePacket BuildAgentAlertPacket(string message, bool modal) { AgentAlertMessagePacket alertPack = (AgentAlertMessagePacket)PacketPool.Instance.GetPacket(PacketType.AgentAlertMessage); alertPack.AgentData.AgentID = AgentId; @@ -3533,7 +3489,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP objectData.FullID = objectID; objectData.OwnerID = ownerID; - objectData.Text = LLUtil.StringToPacketBytes(text); + objectData.Text = Util.StringToBytes256(text); objectData.TextColor[0] = color[0]; objectData.TextColor[1] = color[1]; objectData.TextColor[2] = color[2]; @@ -3911,8 +3867,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP objPropDB.SalePrice = SalePrice; objPropDB.Category = Category; objPropDB.LastOwnerID = LastOwnerID; - objPropDB.Name = LLUtil.StringToPacketBytes(ObjectName); - objPropDB.Description = LLUtil.StringToPacketBytes(Description); + objPropDB.Name = Util.StringToBytes256(ObjectName); + objPropDB.Description = Util.StringToBytes256(Description); objPropFamilyPack.ObjectData = objPropDB; objPropFamilyPack.Header.Zerocoded = true; OutPacket(objPropFamilyPack, ThrottleOutPacketType.Task); @@ -3946,11 +3902,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP proper.ObjectData[0].OwnerID = UUID.Zero; else proper.ObjectData[0].OwnerID = OwnerUUID; - proper.ObjectData[0].TouchName = LLUtil.StringToPacketBytes(TouchTitle); + proper.ObjectData[0].TouchName = Util.StringToBytes256(TouchTitle); proper.ObjectData[0].TextureID = TextureID; - proper.ObjectData[0].SitName = LLUtil.StringToPacketBytes(SitTitle); - proper.ObjectData[0].Name = LLUtil.StringToPacketBytes(ItemName); - proper.ObjectData[0].Description = LLUtil.StringToPacketBytes(ItemDescription); + proper.ObjectData[0].SitName = Util.StringToBytes256(SitTitle); + proper.ObjectData[0].Name = Util.StringToBytes256(ItemName); + proper.ObjectData[0].Description = Util.StringToBytes256(ItemDescription); proper.ObjectData[0].OwnerMask = OwnerMask; proper.ObjectData[0].NextOwnerMask = NextOwnerMask; proper.ObjectData[0].GroupMask = GroupMask; @@ -4191,11 +4147,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP updatePacket.ParcelData.MediaAutoScale = landData.MediaAutoScale; updatePacket.ParcelData.MediaID = landData.MediaID; - updatePacket.ParcelData.MediaURL = LLUtil.StringToPacketBytes(landData.MediaURL); - updatePacket.ParcelData.MusicURL = LLUtil.StringToPacketBytes(landData.MusicURL); - updatePacket.ParcelData.Name = Utils.StringToBytes(landData.Name); + updatePacket.ParcelData.MediaURL = Util.StringToBytes256(landData.MediaURL); + updatePacket.ParcelData.MusicURL = Util.StringToBytes256(landData.MusicURL); + updatePacket.ParcelData.Name = Util.StringToBytes256(landData.Name); updatePacket.ParcelData.OtherCleanTime = landData.OtherCleanTime; - updatePacket.ParcelData.OtherCount = 0; //unemplemented + updatePacket.ParcelData.OtherCount = 0; //TODO: Unimplemented updatePacket.ParcelData.OtherPrims = landData.OtherPrims; updatePacket.ParcelData.OwnerID = landData.OwnerID; updatePacket.ParcelData.OwnerPrims = landData.OwnerPrims; @@ -4203,22 +4159,18 @@ namespace OpenSim.Region.ClientStack.LindenUDP updatePacket.ParcelData.ParcelPrimBonus = simObjectBonusFactor; updatePacket.ParcelData.PassHours = landData.PassHours; updatePacket.ParcelData.PassPrice = landData.PassPrice; - updatePacket.ParcelData.PublicCount = 0; //unemplemented + updatePacket.ParcelData.PublicCount = 0; //TODO: Unimplemented - updatePacket.ParcelData.RegionDenyAnonymous = ((regionFlags & (uint)RegionFlags.DenyAnonymous) > - 0); - updatePacket.ParcelData.RegionDenyIdentified = ((regionFlags & (uint)RegionFlags.DenyIdentified) > - 0); - updatePacket.ParcelData.RegionDenyTransacted = ((regionFlags & (uint)RegionFlags.DenyTransacted) > - 0); - updatePacket.ParcelData.RegionPushOverride = ((regionFlags & (uint)RegionFlags.RestrictPushObject) > - 0); + updatePacket.ParcelData.RegionDenyAnonymous = (regionFlags & (uint)RegionFlags.DenyAnonymous) > 0; + updatePacket.ParcelData.RegionDenyIdentified = (regionFlags & (uint)RegionFlags.DenyIdentified) > 0; + updatePacket.ParcelData.RegionDenyTransacted = (regionFlags & (uint)RegionFlags.DenyTransacted) > 0; + updatePacket.ParcelData.RegionPushOverride = (regionFlags & (uint)RegionFlags.RestrictPushObject) > 0; updatePacket.ParcelData.RentPrice = 0; updatePacket.ParcelData.RequestResult = request_result; updatePacket.ParcelData.SalePrice = landData.SalePrice; updatePacket.ParcelData.SelectedPrims = landData.SelectedPrims; - updatePacket.ParcelData.SelfCount = 0; //unemplemented + updatePacket.ParcelData.SelfCount = 0; //TODO: Unimplemented updatePacket.ParcelData.SequenceID = sequence_id; if (landData.SimwideArea > 0) { @@ -5265,18 +5217,18 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_udpClient.SetThrottles(throttles); } + /// + /// Get the current throttles for this client as a packed byte array + /// + /// Unused + /// public byte[] GetThrottlesPacked(float multiplier) { return m_udpClient.GetThrottlesPacked(); } - public bool IsThrottleEmpty(ThrottleOutPacketType category) - { - return m_udpClient.IsThrottleEmpty(category); - } - /// - /// Unused + /// Cruft? /// public virtual void InPacket(object NewPack) { @@ -6231,14 +6183,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP break; case PacketType.AgentPause: - m_probesWithNoIngressPackets = 0; - m_clientBlocked = true; + m_udpClient.IsPaused = true; break; case PacketType.AgentResume: - m_probesWithNoIngressPackets = 0; - m_clientBlocked = false; - SendStartPingCheck(0); + m_udpClient.IsPaused = false; + SendStartPingCheck(m_udpClient.CurrentPingSequence++); break; @@ -8904,14 +8854,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP #region unimplemented handlers case PacketType.StartPingCheck: - // Send the client the ping response back - // Pass the same PingID in the matching packet - // Handled In the packet processing - //m_log.Debug("[CLIENT]: possibly unhandled StartPingCheck packet"); + StartPingCheckPacket pingStart = (StartPingCheckPacket)Pack; + CompletePingCheckPacket pingComplete = new CompletePingCheckPacket(); + pingComplete.PingID.PingID = pingStart.PingID.PingID; + m_udpServer.SendPacket(m_udpClient, pingComplete, ThrottleOutPacketType.Unknown, false); break; + case PacketType.CompletePingCheck: - // TODO: Perhaps this should be processed on the Sim to determine whether or not to drop a dead client - //m_log.Warn("[CLIENT]: unhandled CompletePingCheck packet"); + // TODO: Do stats tracking or something with these? break; case PacketType.ViewerStats: @@ -10209,14 +10159,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP return info; } - public EndPoint GetClientEP() + public void SetClientInfo(ClientInfo info) { - return m_userEndPoint; + m_udpClient.SetClientInfo(info); } - public void SetClientInfo(ClientInfo info) + public EndPoint GetClientEP() { - m_udpClient.SetClientInfo(info); + return m_userEndPoint; } #region Media Parcel Members diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLPacketHandler.cs b/OpenSim/Region/ClientStack/LindenUDP/LLPacketHandler.cs deleted file mode 100644 index f30df4d..0000000 --- a/OpenSim/Region/ClientStack/LindenUDP/LLPacketHandler.cs +++ /dev/null @@ -1,875 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Reflection; -using System.Collections.Generic; -using System.Net.Sockets; -using System.Threading; -using System.Timers; -using OpenMetaverse; -using OpenMetaverse.Packets; -using log4net; -using OpenSim.Framework; -using Timer=System.Timers.Timer; - -namespace OpenSim.Region.ClientStack.LindenUDP -{ - public delegate void PacketStats(int inPackets, int outPackets, int unAckedBytes); - public delegate void PacketDrop(Packet pack, Object id); - public delegate void QueueEmpty(ThrottleOutPacketType queue); - public delegate bool SynchronizeClientHandler(IScene scene, Packet packet, UUID agentID, ThrottleOutPacketType throttlePacketType); - - public class LLPacketHandler - { - private static readonly ILog m_log - = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - //private int m_resentCount; - - // Packet queues - // - LLPacketQueue m_PacketQueue; - - public LLPacketQueue PacketQueue - { - get { return m_PacketQueue; } - } - - // Timer to run stats and acks on - // - private Timer m_AckTimer = new Timer(250); - - // A list of the packets we haven't acked yet - // - private List m_PendingAcks = new List(); - private Dictionary m_PendingAcksMap = new Dictionary(); - - private Dictionary m_NeedAck = - new Dictionary(); - - /// - /// The number of milliseconds that can pass before a packet that needs an ack is resent. - /// - private uint m_ResendTimeout = 4000; - - public uint ResendTimeout - { - get { return m_ResendTimeout; } - set { m_ResendTimeout = value; } - } - - private int m_MaxReliableResends = 3; - - public int MaxReliableResends - { - get { return m_MaxReliableResends; } - set { m_MaxReliableResends = value; } - } - - // Track duplicated packets. This uses a Dictionary. Both insertion - // and lookup are common operations and need to take advantage of - // the hashing. Expiration is less common and can be allowed the - // time for a linear scan. - // - private List m_alreadySeenList = new List(); - private Dictionarym_alreadySeenTracker = new Dictionary(); - private int m_alreadySeenWindow = 30000; - private int m_lastAlreadySeenCheck = Environment.TickCount & Int32.MaxValue; - - // private Dictionary m_DupeTracker = - // new Dictionary(); - // private uint m_DupeTrackerWindow = 30; - // private int m_DupeTrackerLastCheck = Environment.TickCount; - - // Values for the SimStatsReporter - // - private int m_PacketsReceived = 0; - private int m_PacketsReceivedReported = 0; - private int m_PacketsSent = 0; - private int m_PacketsSentReported = 0; - private int m_UnackedBytes = 0; - - private int m_LastResend = 0; - - public int PacketsReceived - { - get { return m_PacketsReceived; } - } - - public int PacketsReceivedReported - { - get { return m_PacketsReceivedReported; } - } - - // The client we are working for - // - private IClientAPI m_Client; - - // Some events - // - public event PacketStats OnPacketStats; - public event PacketDrop OnPacketDrop; - public event QueueEmpty OnQueueEmpty; - - - //private SynchronizeClientHandler m_SynchronizeClient = null; - - public SynchronizeClientHandler SynchronizeClient - { - set { /* m_SynchronizeClient = value; */ } - } - - // Packet sequencing - // - private uint m_Sequence = 0; - private object m_SequenceLock = new object(); - private const int MAX_SEQUENCE = 0xFFFFFF; - - // Packet dropping - // - List m_ImportantPackets = new List(); - private bool m_ReliableIsImportant = false; - - public bool ReliableIsImportant - { - get { return m_ReliableIsImportant; } - set { m_ReliableIsImportant = value; } - } - - private int m_DropSafeTimeout; - - LLPacketServer m_PacketServer; - private byte[] m_ZeroOutBuffer = new byte[4096]; - - //////////////////////////////////////////////////////////////////// - - // Constructors - // - public LLPacketHandler(IClientAPI client, LLPacketServer server, ClientStackUserSettings userSettings) - { - m_Client = client; - m_PacketServer = server; - m_DropSafeTimeout = Environment.TickCount + 15000; - - m_PacketQueue = new LLPacketQueue(client.AgentId, userSettings); - - m_PacketQueue.OnQueueEmpty += TriggerOnQueueEmpty; - - m_AckTimer.Elapsed += AckTimerElapsed; - m_AckTimer.Start(); - } - - public void Dispose() - { - m_AckTimer.Stop(); - m_AckTimer.Close(); - - m_PacketQueue.Enqueue(null); - m_PacketQueue.Close(); - m_Client = null; - } - - // Send one packet. This actually doesn't send anything, it queues - // it. Designed to be fire-and-forget, but there is an optional - // notifier. - // - public void OutPacket( - Packet packet, ThrottleOutPacketType throttlePacketType) - { - OutPacket(packet, throttlePacketType, null); - } - - public void OutPacket( - Packet packet, ThrottleOutPacketType throttlePacketType, - Object id) - { - // Call the load balancer's hook. If this is not active here - // we defer to the sim server this client is actually connected - // to. Packet drop notifies will not be triggered in this - // configuration! - // - - packet.Header.Sequence = 0; - - lock (m_NeedAck) - { - DropResend(id); - - AddAcks(ref packet); - QueuePacket(packet, throttlePacketType, id); - } - } - - private void AddAcks(ref Packet packet) - { - // These packet types have shown to have issues with - // acks being appended to the payload, just don't send - // any with them until libsl is fixed. - // - if (packet is ViewerEffectPacket) - return; - if (packet is SimStatsPacket) - return; - - // Add acks to outgoing packets - // - if (m_PendingAcks.Count > 0) - { - int count = m_PendingAcks.Count; - if (count > 10) - count = 10; - packet.Header.AckList = new uint[count]; - packet.Header.AppendedAcks = true; - - for (int i = 0; i < count; i++) - { - packet.Header.AckList[i] = m_PendingAcks[i]; - m_PendingAcksMap.Remove(m_PendingAcks[i]); - } - m_PendingAcks.RemoveRange(0, count); - } - } - - private void QueuePacket( - Packet packet, ThrottleOutPacketType throttlePacketType, - Object id) - { - LLQueItem item = new LLQueItem(); - item.Packet = packet; - item.Incoming = false; - item.throttleType = throttlePacketType; - item.TickCount = Environment.TickCount; - item.Identifier = id; - item.Resends = 0; - item.Length = packet.Length; - item.Sequence = packet.Header.Sequence; - - m_PacketQueue.Enqueue(item); - m_PacketsSent++; - } - - private void ResendUnacked() - { - int now = Environment.TickCount; - - int intervalMs = 250; - - if (m_LastResend != 0) - intervalMs = now - m_LastResend; - - lock (m_NeedAck) - { - if (m_DropSafeTimeout > now || - intervalMs > 500) // We were frozen! - { - foreach (LLQueItem data in m_NeedAck.Values) - { - if (m_DropSafeTimeout > now) - { - m_NeedAck[data.Packet.Header.Sequence].TickCount = now; - } - else - { - m_NeedAck[data.Packet.Header.Sequence].TickCount += intervalMs; - } - } - } - - m_LastResend = now; - - // Unless we have received at least one ack, don't bother resending - // anything. There may not be a client there, don't clog up the - // pipes. - - - // Nothing to do - // - if (m_NeedAck.Count == 0) - return; - - int resent = 0; - long dueDate = now - m_ResendTimeout; - - List dropped = new List(); - foreach (LLQueItem data in m_NeedAck.Values) - { - Packet packet = data.Packet; - - // Packets this old get resent - // - if (data.TickCount < dueDate && data.Sequence != 0 && !m_PacketQueue.Contains(data.Sequence)) - { - if (resent < 20) // Was 20 (= Max 117kbit/sec resends) - { - m_NeedAck[packet.Header.Sequence].Resends++; - - // The client needs to be told that a packet is being resent, otherwise it appears to believe - // that it should reset its sequence to that packet number. - packet.Header.Resent = true; - - if ((m_NeedAck[packet.Header.Sequence].Resends >= m_MaxReliableResends) && - (!m_ReliableIsImportant)) - { - dropped.Add(data); - continue; - } - - m_NeedAck[packet.Header.Sequence].TickCount = Environment.TickCount; - QueuePacket(packet, ThrottleOutPacketType.Resend, data.Identifier); - resent++; - } - else - { - m_NeedAck[packet.Header.Sequence].TickCount += intervalMs; - } - } - } - - foreach (LLQueItem data in dropped) - { - m_NeedAck.Remove(data.Packet.Header.Sequence); - TriggerOnPacketDrop(data.Packet, data.Identifier); - m_PacketQueue.Cancel(data.Packet.Header.Sequence); - PacketPool.Instance.ReturnPacket(data.Packet); - } - } - } - - // Send the pending packet acks to the client - // Will send blocks of acks for up to 250 packets - // - private void SendAcks() - { - lock (m_NeedAck) - { - if (m_PendingAcks.Count == 0) - return; - - PacketAckPacket acks = (PacketAckPacket)PacketPool.Instance.GetPacket(PacketType.PacketAck); - - // The case of equality is more common than one might think, - // because this function will be called unconditionally when - // the counter reaches 250. So there is a good chance another - // packet with 250 blocks exists. - // - if (acks.Packets == null || - acks.Packets.Length != m_PendingAcks.Count) - acks.Packets = new PacketAckPacket.PacketsBlock[m_PendingAcks.Count]; - - for (int i = 0; i < m_PendingAcks.Count; i++) - { - acks.Packets[i] = new PacketAckPacket.PacketsBlock(); - acks.Packets[i].ID = m_PendingAcks[i]; - - } - m_PendingAcks.Clear(); - m_PendingAcksMap.Clear(); - - acks.Header.Reliable = false; - OutPacket(acks, ThrottleOutPacketType.Unknown); - } - } - - // Queue a packet ack. It will be sent either after 250 acks are - // queued, or when the timer fires. - // - private void AckPacket(Packet packet) - { - lock (m_NeedAck) - { - if (m_PendingAcks.Count < 250) - { - if (!m_PendingAcksMap.ContainsKey(packet.Header.Sequence)) - { - m_PendingAcks.Add(packet.Header.Sequence); - m_PendingAcksMap.Add(packet.Header.Sequence, - packet.Header.Sequence); - } - return; - } - } - - SendAcks(); - - lock (m_NeedAck) - { - // If this is still full we have a truly exceptional - // condition (means, can't happen) - // - if (m_PendingAcks.Count < 250) - { - if (!m_PendingAcksMap.ContainsKey(packet.Header.Sequence)) - { - m_PendingAcks.Add(packet.Header.Sequence); - m_PendingAcksMap.Add(packet.Header.Sequence, - packet.Header.Sequence); - } - return; - } - } - } - - // When the timer elapses, send the pending acks, trigger resends - // and report all the stats. - // - private void AckTimerElapsed(object sender, ElapsedEventArgs ea) - { - SendAcks(); - ResendUnacked(); - SendPacketStats(); - } - - // Push out pachet counts for the sim status reporter - // - private void SendPacketStats() - { - PacketStats handlerPacketStats = OnPacketStats; - if (handlerPacketStats != null) - { - handlerPacketStats( - m_PacketsReceived - m_PacketsReceivedReported, - m_PacketsSent - m_PacketsSentReported, - m_UnackedBytes); - - m_PacketsReceivedReported = m_PacketsReceived; - m_PacketsSentReported = m_PacketsSent; - } - } - - // We can't keep an unlimited record of dupes. This will prune the - // dictionary by age. - // - // NOTE: this needs to be called from within lock - // (m_alreadySeenTracker) context! - private void ExpireSeenPackets() - { - if (m_alreadySeenList.Count < 1024) - return; - - int ticks = 0; - int tc = Environment.TickCount & Int32.MaxValue; - if (tc >= m_lastAlreadySeenCheck) - ticks = tc - m_lastAlreadySeenCheck; - else - ticks = Int32.MaxValue - m_lastAlreadySeenCheck + tc; - - if (ticks < 2000) return; - m_lastAlreadySeenCheck = tc; - - // we calculate the drop dead tick count here instead of - // in the loop: any packet with a timestamp before - // dropDeadTC can be expired - int dropDeadTC = tc - m_alreadySeenWindow; - int i = 0; - while (i < m_alreadySeenList.Count && m_alreadySeenTracker[m_alreadySeenList[i]] < dropDeadTC) - { - m_alreadySeenTracker.Remove(m_alreadySeenList[i]); - i++; - } - // if we dropped packet from m_alreadySeenTracker we need - // to drop them from m_alreadySeenList as well, let's do - // that in one go: the list is ordered after all. - if (i > 0) - { - m_alreadySeenList.RemoveRange(0, i); - // m_log.DebugFormat("[CLIENT]: expired {0} packets, {1}:{2} left", i, m_alreadySeenList.Count, m_alreadySeenTracker.Count); - } - } - - public void InPacket(Packet packet) - { - if (packet == null) - return; - - // When too many acks are needed to be sent, the client sends - // a packet consisting of acks only - // - if (packet.Type == PacketType.PacketAck) - { - PacketAckPacket ackPacket = (PacketAckPacket)packet; - - foreach (PacketAckPacket.PacketsBlock block in ackPacket.Packets) - { - ProcessAck(block.ID); - } - - PacketPool.Instance.ReturnPacket(packet); - return; - } - - // Any packet can have some packet acks in the header. - // Process them here - // - if (packet.Header.AppendedAcks) - { - foreach (uint id in packet.Header.AckList) - { - ProcessAck(id); - } - } - - // If this client is on another partial instance, no need - // to handle packets - // - if (!m_Client.IsActive && packet.Type != PacketType.LogoutRequest) - { - PacketPool.Instance.ReturnPacket(packet); - return; - } - - if (packet.Type == PacketType.StartPingCheck) - { - StartPingCheckPacket startPing = (StartPingCheckPacket)packet; - CompletePingCheckPacket endPing - = (CompletePingCheckPacket)PacketPool.Instance.GetPacket(PacketType.CompletePingCheck); - - endPing.PingID.PingID = startPing.PingID.PingID; - OutPacket(endPing, ThrottleOutPacketType.Task); - } - else - { - LLQueItem item = new LLQueItem(); - item.Packet = packet; - item.Incoming = true; - m_PacketQueue.Enqueue(item); - } - } - - public void ProcessInPacket(LLQueItem item) - { - Packet packet = item.Packet; - - // Always ack the packet! - // - if (packet.Header.Reliable) - AckPacket(packet); - - if (packet.Type != PacketType.AgentUpdate) - m_PacketsReceived++; - - // Check for duplicate packets.. packets that the client is - // resending because it didn't receive our ack - // - lock (m_alreadySeenTracker) - { - ExpireSeenPackets(); - - if (m_alreadySeenTracker.ContainsKey(packet.Header.Sequence)) - return; - - m_alreadySeenTracker.Add(packet.Header.Sequence, Environment.TickCount & Int32.MaxValue); - m_alreadySeenList.Add(packet.Header.Sequence); - } - - m_Client.ProcessInPacket(packet); - } - - public void Flush() - { - m_PacketQueue.Flush(); - m_UnackedBytes = (-1 * m_UnackedBytes); - SendPacketStats(); - } - - public void Clear() - { - m_UnackedBytes = (-1 * m_UnackedBytes); - SendPacketStats(); - lock (m_NeedAck) - { - m_NeedAck.Clear(); - m_PendingAcks.Clear(); - m_PendingAcksMap.Clear(); - } - m_Sequence += 1000000; - } - - private void ProcessAck(uint id) - { - LLQueItem data; - - lock (m_NeedAck) - { - //m_log.DebugFormat("[CLIENT]: In {0} received ack for packet {1}", m_Client.Scene.RegionInfo.ExternalEndPoint.Port, id); - - if (!m_NeedAck.TryGetValue(id, out data)) - return; - - m_NeedAck.Remove(id); - m_PacketQueue.Cancel(data.Sequence); - PacketPool.Instance.ReturnPacket(data.Packet); - m_UnackedBytes -= data.Length; - } - } - - // Allocate packet sequence numbers in a threadsave manner - // - protected uint NextPacketSequenceNumber() - { - // Set the sequence number - uint seq = 1; - lock (m_SequenceLock) - { - if (m_Sequence >= MAX_SEQUENCE) - { - m_Sequence = 1; - } - else - { - m_Sequence++; - } - seq = m_Sequence; - } - return seq; - } - - public ClientInfo GetClientInfo() - { - ClientInfo info = new ClientInfo(); - - info.pendingAcks = m_PendingAcksMap; - info.needAck = new Dictionary(); - - lock (m_NeedAck) - { - foreach (uint key in m_NeedAck.Keys) - info.needAck.Add(key, m_NeedAck[key].Packet.ToBytes()); - } - - LLQueItem[] queitems = m_PacketQueue.GetQueueArray(); - - for (int i = 0; i < queitems.Length; i++) - { - if (queitems[i].Incoming == false) - info.out_packets.Add(queitems[i].Packet.ToBytes()); - } - - info.sequence = m_Sequence; - - float multiplier = m_PacketQueue.ThrottleMultiplier; - info.resendThrottle = (int) (m_PacketQueue.ResendThrottle.Throttle / multiplier); - info.landThrottle = (int) (m_PacketQueue.LandThrottle.Throttle / multiplier); - info.windThrottle = (int) (m_PacketQueue.WindThrottle.Throttle / multiplier); - info.cloudThrottle = (int) (m_PacketQueue.CloudThrottle.Throttle / multiplier); - info.taskThrottle = (int) (m_PacketQueue.TaskThrottle.Throttle / multiplier); - info.assetThrottle = (int) (m_PacketQueue.AssetThrottle.Throttle / multiplier); - info.textureThrottle = (int) (m_PacketQueue.TextureThrottle.Throttle / multiplier); - info.totalThrottle = (int) (m_PacketQueue.TotalThrottle.Throttle / multiplier); - - return info; - } - - public void SetClientInfo(ClientInfo info) - { - m_PendingAcksMap = info.pendingAcks; - m_PendingAcks = new List(m_PendingAcksMap.Keys); - m_NeedAck = new Dictionary(); - - Packet packet = null; - int packetEnd = 0; - byte[] zero = new byte[3000]; - - foreach (uint key in info.needAck.Keys) - { - byte[] buff = info.needAck[key]; - packetEnd = buff.Length - 1; - - try - { - packet = PacketPool.Instance.GetPacket(buff, ref packetEnd, zero); - } - catch (Exception) - { - } - - LLQueItem item = new LLQueItem(); - item.Packet = packet; - item.Incoming = false; - item.throttleType = 0; - item.TickCount = Environment.TickCount; - item.Identifier = 0; - item.Resends = 0; - item.Length = packet.Length; - item.Sequence = packet.Header.Sequence; - m_NeedAck.Add(key, item); - } - - m_Sequence = info.sequence; - - m_PacketQueue.ResendThrottle.Throttle = info.resendThrottle; - m_PacketQueue.LandThrottle.Throttle = info.landThrottle; - m_PacketQueue.WindThrottle.Throttle = info.windThrottle; - m_PacketQueue.CloudThrottle.Throttle = info.cloudThrottle; - m_PacketQueue.TaskThrottle.Throttle = info.taskThrottle; - m_PacketQueue.AssetThrottle.Throttle = info.assetThrottle; - m_PacketQueue.TextureThrottle.Throttle = info.textureThrottle; - m_PacketQueue.TotalThrottle.Throttle = info.totalThrottle; - } - - public void AddImportantPacket(PacketType type) - { - if (m_ImportantPackets.Contains(type)) - return; - - m_ImportantPackets.Add(type); - } - - public void RemoveImportantPacket(PacketType type) - { - if (!m_ImportantPackets.Contains(type)) - return; - - m_ImportantPackets.Remove(type); - } - - private void DropResend(Object id) - { - LLQueItem d = null; - - foreach (LLQueItem data in m_NeedAck.Values) - { - if (data.Identifier != null && data.Identifier == id) - { - d = data; - break; - } - } - - if (null == d) return; - - m_NeedAck.Remove(d.Packet.Header.Sequence); - m_PacketQueue.Cancel(d.Sequence); - PacketPool.Instance.ReturnPacket(d.Packet); - } - - private void TriggerOnPacketDrop(Packet packet, Object id) - { - PacketDrop handlerPacketDrop = OnPacketDrop; - - if (handlerPacketDrop == null) - return; - - handlerPacketDrop(packet, id); - } - - private void TriggerOnQueueEmpty(ThrottleOutPacketType queue) - { - QueueEmpty handlerQueueEmpty = OnQueueEmpty; - - if (handlerQueueEmpty != null) - handlerQueueEmpty(queue); - } - - // Convert the packet to bytes and stuff it onto the send queue - // - public void ProcessOutPacket(LLQueItem item) - { - Packet packet = item.Packet; - - // Assign sequence number here to prevent out of order packets - if (packet.Header.Sequence == 0) - { - lock (m_NeedAck) - { - packet.Header.Sequence = NextPacketSequenceNumber(); - item.Sequence = packet.Header.Sequence; - item.TickCount = Environment.TickCount; - - // We want to see that packet arrive if it's reliable - if (packet.Header.Reliable) - { - m_UnackedBytes += item.Length; - - // Keep track of when this packet was sent out - item.TickCount = Environment.TickCount; - - m_NeedAck[packet.Header.Sequence] = item; - } - } - } - - // If we sent a killpacket - if (packet is KillPacket) - Abort(); - - try - { - // If this packet has been reused/returned, the ToBytes - // will blow up in our face. - // Fail gracefully. - // - - // Actually make the byte array and send it - byte[] sendbuffer = item.Packet.ToBytes(); - - if (packet.Header.Zerocoded) - { - int packetsize = Helpers.ZeroEncode(sendbuffer, - sendbuffer.Length, m_ZeroOutBuffer); - m_PacketServer.SendPacketTo(m_ZeroOutBuffer, packetsize, - SocketFlags.None, m_Client.CircuitCode); - } - else - { - // Need some extra space in case we need to add proxy - // information to the message later - Buffer.BlockCopy(sendbuffer, 0, m_ZeroOutBuffer, 0, - sendbuffer.Length); - m_PacketServer.SendPacketTo(m_ZeroOutBuffer, - sendbuffer.Length, SocketFlags.None, m_Client.CircuitCode); - } - } - catch (NullReferenceException) - { - m_log.Error("[PACKET]: Detected reuse of a returned packet"); - m_PacketQueue.Cancel(item.Sequence); - return; - } - - // If this is a reliable packet, we are still holding a ref - // Dont't return in that case - // - if (!packet.Header.Reliable) - { - m_PacketQueue.Cancel(item.Sequence); - PacketPool.Instance.ReturnPacket(packet); - } - } - - private void Abort() - { - m_PacketQueue.Close(); - Thread.CurrentThread.Abort(); - } - - public int GetQueueCount(ThrottleOutPacketType queue) - { - return m_PacketQueue.GetQueueCount(queue); - } - } -} diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLPacketQueue.cs b/OpenSim/Region/ClientStack/LindenUDP/LLPacketQueue.cs deleted file mode 100644 index 3eed2e0..0000000 --- a/OpenSim/Region/ClientStack/LindenUDP/LLPacketQueue.cs +++ /dev/null @@ -1,742 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Threading; -using System.Timers; -using log4net; -using OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Framework.Statistics; -using OpenSim.Framework.Statistics.Interfaces; -using Timer=System.Timers.Timer; - -namespace OpenSim.Region.ClientStack.LindenUDP -{ - public class LLPacketQueue : IPullStatsProvider, IDisposable - { - private static readonly ILog m_log - = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - /// - /// Is queueing enabled at all? - /// - private bool m_enabled = true; - - private OpenSim.Framework.BlockingQueue SendQueue; - - private Queue IncomingPacketQueue; - private Queue OutgoingPacketQueue; - private Queue ResendOutgoingPacketQueue; - private Queue LandOutgoingPacketQueue; - private Queue WindOutgoingPacketQueue; - private Queue CloudOutgoingPacketQueue; - private Queue TaskOutgoingPacketQueue; - private Queue TaskLowpriorityPacketQueue; - private Queue TextureOutgoingPacketQueue; - private Queue AssetOutgoingPacketQueue; - - private List Empty = new List(); - // m_log.Info("[THROTTLE]: Entering Throttle"); - // private Dictionary PendingAcks = new Dictionary(); - // private Dictionary NeedAck = new Dictionary(); - - // All throttle times and number of bytes are calculated by dividing by this value - // This value also determines how many times per throttletimems the timer will run - // If throttleimems is 1000 ms, then the timer will fire every 1000/7 milliseconds - - private float throttleMultiplier = 2.0f; // Default value really doesn't matter. - private int throttleTimeDivisor = 7; - - private int throttletimems = 1000; - - internal LLPacketThrottle ResendThrottle; - internal LLPacketThrottle LandThrottle; - internal LLPacketThrottle WindThrottle; - internal LLPacketThrottle CloudThrottle; - internal LLPacketThrottle TaskThrottle; - internal LLPacketThrottle AssetThrottle; - internal LLPacketThrottle TextureThrottle; - internal LLPacketThrottle TotalThrottle; - - private Dictionary contents = new Dictionary(); - - // private long LastThrottle; - // private long ThrottleInterval; - private Timer throttleTimer; - - private UUID m_agentId; - - public event QueueEmpty OnQueueEmpty; - - public LLPacketQueue(UUID agentId, ClientStackUserSettings userSettings) - { - // While working on this, the BlockingQueue had me fooled for a bit. - // The Blocking queue causes the thread to stop until there's something - // in it to process. it's an on-purpose threadlock though because - // without it, the clientloop will suck up all sim resources. - - SendQueue = new OpenSim.Framework.BlockingQueue(); - - IncomingPacketQueue = new Queue(); - OutgoingPacketQueue = new Queue(); - ResendOutgoingPacketQueue = new Queue(); - LandOutgoingPacketQueue = new Queue(); - WindOutgoingPacketQueue = new Queue(); - CloudOutgoingPacketQueue = new Queue(); - TaskOutgoingPacketQueue = new Queue(); - TaskLowpriorityPacketQueue = new Queue(); - TextureOutgoingPacketQueue = new Queue(); - AssetOutgoingPacketQueue = new Queue(); - - // Store the throttle multiplier for posterity. - throttleMultiplier = userSettings.ClientThrottleMultipler; - - - int throttleMaxBPS = 1500000; - if (userSettings.TotalThrottleSettings != null) - throttleMaxBPS = userSettings.TotalThrottleSettings.Max; - - // Set up the throttle classes (min, max, current) in bits per second - ResendThrottle = new LLPacketThrottle(5000, throttleMaxBPS / 15, 16000, userSettings.ClientThrottleMultipler); - LandThrottle = new LLPacketThrottle(1000, throttleMaxBPS / 15, 2000, userSettings.ClientThrottleMultipler); - WindThrottle = new LLPacketThrottle(0, throttleMaxBPS / 15, 0, userSettings.ClientThrottleMultipler); - CloudThrottle = new LLPacketThrottle(0, throttleMaxBPS / 15, 0, userSettings.ClientThrottleMultipler); - TaskThrottle = new LLPacketThrottle(1000, throttleMaxBPS / 2, 3000, userSettings.ClientThrottleMultipler); - AssetThrottle = new LLPacketThrottle(1000, throttleMaxBPS / 2, 1000, userSettings.ClientThrottleMultipler); - TextureThrottle = new LLPacketThrottle(1000, throttleMaxBPS / 2, 4000, userSettings.ClientThrottleMultipler); - - - // Total Throttle trumps all - it is the number of bits in total that are allowed to go out per second. - - - ThrottleSettings totalThrottleSettings = userSettings.TotalThrottleSettings; - if (null == totalThrottleSettings) - { - totalThrottleSettings = new ThrottleSettings(0, throttleMaxBPS, 28000); - } - - TotalThrottle - = new LLPacketThrottle( - totalThrottleSettings.Min, totalThrottleSettings.Max, totalThrottleSettings.Current, - userSettings.ClientThrottleMultipler); - - throttleTimer = new Timer((int)(throttletimems / throttleTimeDivisor)); - throttleTimer.Elapsed += ThrottleTimerElapsed; - throttleTimer.Start(); - - // TIMERS needed for this - // LastThrottle = DateTime.Now.Ticks; - // ThrottleInterval = (long)(throttletimems/throttleTimeDivisor); - - m_agentId = agentId; - - if (StatsManager.SimExtraStats != null) - { - StatsManager.SimExtraStats.RegisterPacketQueueStatsProvider(m_agentId, this); - } - } - - /* STANDARD QUEUE MANIPULATION INTERFACES */ - - public void Enqueue(LLQueItem item) - { - if (!m_enabled) - { - return; - } - // We could micro lock, but that will tend to actually - // probably be worse than just synchronizing on SendQueue - - if (item == null) - { - SendQueue.Enqueue(item); - return; - } - - if (item.Incoming) - { - SendQueue.PriorityEnqueue(item); - return; - } - - if (item.Sequence != 0) - lock (contents) - { - if (contents.ContainsKey(item.Sequence)) - contents[item.Sequence] += 1; - else - contents.Add(item.Sequence, 1); - } - - lock (this) - { - switch (item.throttleType & ThrottleOutPacketType.TypeMask) - { - case ThrottleOutPacketType.Resend: - ThrottleCheck(ref ResendThrottle, ref ResendOutgoingPacketQueue, item, ThrottleOutPacketType.Resend); - break; - case ThrottleOutPacketType.Texture: - ThrottleCheck(ref TextureThrottle, ref TextureOutgoingPacketQueue, item, ThrottleOutPacketType.Texture); - break; - case ThrottleOutPacketType.Task: - if ((item.throttleType & ThrottleOutPacketType.LowPriority) != 0) - ThrottleCheck(ref TaskThrottle, ref TaskLowpriorityPacketQueue, item, ThrottleOutPacketType.Task); - else - ThrottleCheck(ref TaskThrottle, ref TaskOutgoingPacketQueue, item, ThrottleOutPacketType.Task); - break; - case ThrottleOutPacketType.Land: - ThrottleCheck(ref LandThrottle, ref LandOutgoingPacketQueue, item, ThrottleOutPacketType.Land); - break; - case ThrottleOutPacketType.Asset: - ThrottleCheck(ref AssetThrottle, ref AssetOutgoingPacketQueue, item, ThrottleOutPacketType.Asset); - break; - case ThrottleOutPacketType.Cloud: - ThrottleCheck(ref CloudThrottle, ref CloudOutgoingPacketQueue, item, ThrottleOutPacketType.Cloud); - break; - case ThrottleOutPacketType.Wind: - ThrottleCheck(ref WindThrottle, ref WindOutgoingPacketQueue, item, ThrottleOutPacketType.Wind); - break; - - default: - // Acknowledgements and other such stuff should go directly to the blocking Queue - // Throttling them may and likely 'will' be problematic - SendQueue.PriorityEnqueue(item); - break; - } - } - } - - public LLQueItem Dequeue() - { - while (true) - { - LLQueItem item = SendQueue.Dequeue(); - if (item == null) - return null; - if (item.Incoming) - return item; - item.TickCount = System.Environment.TickCount; - if (item.Sequence == 0) - return item; - lock (contents) - { - if (contents.ContainsKey(item.Sequence)) - { - if (contents[item.Sequence] == 1) - contents.Remove(item.Sequence); - else - contents[item.Sequence] -= 1; - return item; - } - } - } - } - - public void Cancel(uint sequence) - { - lock (contents) contents.Remove(sequence); - } - - public bool Contains(uint sequence) - { - lock (contents) return contents.ContainsKey(sequence); - } - - public void Flush() - { - lock (this) - { - // These categories do not contain transactional packets so we can safely drop any pending data in them - LandOutgoingPacketQueue.Clear(); - WindOutgoingPacketQueue.Clear(); - CloudOutgoingPacketQueue.Clear(); - TextureOutgoingPacketQueue.Clear(); - AssetOutgoingPacketQueue.Clear(); - - // Now comes the fun part.. we dump all remaining resend and task packets into the send queue - while (ResendOutgoingPacketQueue.Count > 0 || TaskOutgoingPacketQueue.Count > 0 || TaskLowpriorityPacketQueue.Count > 0) - { - if (ResendOutgoingPacketQueue.Count > 0) - SendQueue.Enqueue(ResendOutgoingPacketQueue.Dequeue()); - - if (TaskOutgoingPacketQueue.Count > 0) - SendQueue.PriorityEnqueue(TaskOutgoingPacketQueue.Dequeue()); - - if (TaskLowpriorityPacketQueue.Count > 0) - SendQueue.Enqueue(TaskLowpriorityPacketQueue.Dequeue()); - } - } - } - - public void WipeClean() - { - lock (this) - { - ResendOutgoingPacketQueue.Clear(); - LandOutgoingPacketQueue.Clear(); - WindOutgoingPacketQueue.Clear(); - CloudOutgoingPacketQueue.Clear(); - TaskOutgoingPacketQueue.Clear(); - TaskLowpriorityPacketQueue.Clear(); - TextureOutgoingPacketQueue.Clear(); - AssetOutgoingPacketQueue.Clear(); - SendQueue.Clear(); - lock (contents) contents.Clear(); - } - } - - public void Close() - { - Dispose(); - } - - public void Dispose() - { - Flush(); - WipeClean(); // I'm sure there's a dirty joke in here somewhere. -AFrisby - - m_enabled = false; - throttleTimer.Stop(); - throttleTimer.Close(); - - if (StatsManager.SimExtraStats != null) - { - StatsManager.SimExtraStats.DeregisterPacketQueueStatsProvider(m_agentId); - } - } - - private void ResetCounters() - { - ResendThrottle.Reset(); - LandThrottle.Reset(); - WindThrottle.Reset(); - CloudThrottle.Reset(); - TaskThrottle.Reset(); - AssetThrottle.Reset(); - TextureThrottle.Reset(); - TotalThrottle.Reset(); - } - - private bool PacketsWaiting() - { - return (ResendOutgoingPacketQueue.Count > 0 || - LandOutgoingPacketQueue.Count > 0 || - WindOutgoingPacketQueue.Count > 0 || - CloudOutgoingPacketQueue.Count > 0 || - TaskOutgoingPacketQueue.Count > 0 || - TaskLowpriorityPacketQueue.Count > 0 || - AssetOutgoingPacketQueue.Count > 0 || - TextureOutgoingPacketQueue.Count > 0); - } - - public void ProcessThrottle() - { - // I was considering this.. Will an event fire if the thread it's on is blocked? - - // Then I figured out.. it doesn't really matter.. because this thread won't be blocked for long - // The General overhead of the UDP protocol gets sent to the queue un-throttled by this - // so This'll pick up about around the right time. - - int MaxThrottleLoops = 4550; // 50*7 packets can be dequeued at once. - int throttleLoops = 0; - List e; - - // We're going to dequeue all of the saved up packets until - // we've hit the throttle limit or there's no more packets to send - lock (this) - { - // this variable will be true if there was work done in the last execution of the - // loop, since each pass through the loop checks the queue length, we no longer - // need the check on entering the loop - bool qchanged = true; - - ResetCounters(); - - while (TotalThrottle.UnderLimit() && qchanged && throttleLoops <= MaxThrottleLoops) - { - qchanged = false; // We will break out of the loop if no work was accomplished - - throttleLoops++; - //Now comes the fun part.. we dump all our elements into m_packetQueue that we've saved up. - if ((ResendOutgoingPacketQueue.Count > 0) && ResendThrottle.UnderLimit()) - { - LLQueItem qpack = ResendOutgoingPacketQueue.Dequeue(); - - SendQueue.Enqueue(qpack); - TotalThrottle.AddBytes(qpack.Length); - ResendThrottle.AddBytes(qpack.Length); - - qchanged = true; - } - - if ((LandOutgoingPacketQueue.Count > 0) && LandThrottle.UnderLimit()) - { - LLQueItem qpack = LandOutgoingPacketQueue.Dequeue(); - - SendQueue.Enqueue(qpack); - TotalThrottle.AddBytes(qpack.Length); - LandThrottle.AddBytes(qpack.Length); - qchanged = true; - - if (LandOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Land)) - Empty.Add(ThrottleOutPacketType.Land); - } - - if ((WindOutgoingPacketQueue.Count > 0) && WindThrottle.UnderLimit()) - { - LLQueItem qpack = WindOutgoingPacketQueue.Dequeue(); - - SendQueue.Enqueue(qpack); - TotalThrottle.AddBytes(qpack.Length); - WindThrottle.AddBytes(qpack.Length); - qchanged = true; - - if (WindOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Wind)) - Empty.Add(ThrottleOutPacketType.Wind); - } - - if ((CloudOutgoingPacketQueue.Count > 0) && CloudThrottle.UnderLimit()) - { - LLQueItem qpack = CloudOutgoingPacketQueue.Dequeue(); - - SendQueue.Enqueue(qpack); - TotalThrottle.AddBytes(qpack.Length); - CloudThrottle.AddBytes(qpack.Length); - qchanged = true; - - if (CloudOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Cloud)) - Empty.Add(ThrottleOutPacketType.Cloud); - } - - if ((TaskOutgoingPacketQueue.Count > 0 || TaskLowpriorityPacketQueue.Count > 0) && TaskThrottle.UnderLimit()) - { - LLQueItem qpack; - if (TaskOutgoingPacketQueue.Count > 0) - { - qpack = TaskOutgoingPacketQueue.Dequeue(); - SendQueue.PriorityEnqueue(qpack); - } - else - { - qpack = TaskLowpriorityPacketQueue.Dequeue(); - SendQueue.Enqueue(qpack); - } - - TotalThrottle.AddBytes(qpack.Length); - TaskThrottle.AddBytes(qpack.Length); - qchanged = true; - - if (TaskOutgoingPacketQueue.Count == 0 && TaskLowpriorityPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Task)) - Empty.Add(ThrottleOutPacketType.Task); - } - - if ((TextureOutgoingPacketQueue.Count > 0) && TextureThrottle.UnderLimit()) - { - LLQueItem qpack = TextureOutgoingPacketQueue.Dequeue(); - - SendQueue.Enqueue(qpack); - TotalThrottle.AddBytes(qpack.Length); - TextureThrottle.AddBytes(qpack.Length); - qchanged = true; - - if (TextureOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Texture)) - Empty.Add(ThrottleOutPacketType.Texture); - } - - if ((AssetOutgoingPacketQueue.Count > 0) && AssetThrottle.UnderLimit()) - { - LLQueItem qpack = AssetOutgoingPacketQueue.Dequeue(); - - SendQueue.Enqueue(qpack); - TotalThrottle.AddBytes(qpack.Length); - AssetThrottle.AddBytes(qpack.Length); - qchanged = true; - - if (AssetOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Asset)) - Empty.Add(ThrottleOutPacketType.Asset); - } - } - // m_log.Info("[THROTTLE]: Processed " + throttleLoops + " packets"); - - e = new List(Empty); - Empty.Clear(); - } - - foreach (ThrottleOutPacketType t in e) - { - if (GetQueueCount(t) == 0) - TriggerOnQueueEmpty(t); - } - } - - private void TriggerOnQueueEmpty(ThrottleOutPacketType queue) - { - QueueEmpty handlerQueueEmpty = OnQueueEmpty; - - if (handlerQueueEmpty != null) - handlerQueueEmpty(queue); - } - - private void ThrottleTimerElapsed(object sender, ElapsedEventArgs e) - { - // just to change the signature, and that ProcessThrottle - // will be used elsewhere possibly - ProcessThrottle(); - } - - private void ThrottleCheck(ref LLPacketThrottle throttle, ref Queue q, LLQueItem item, ThrottleOutPacketType itemType) - { - // The idea.. is if the packet throttle queues are empty - // and the client is under throttle for the type. Queue - // it up directly. This basically short cuts having to - // wait for the timer to fire to put things into the - // output queue - - if ((q.Count == 0) && (throttle.UnderLimit())) - { - try - { - Monitor.Enter(this); - throttle.AddBytes(item.Length); - TotalThrottle.AddBytes(item.Length); - SendQueue.Enqueue(item); - lock (this) - { - if (!Empty.Contains(itemType)) - Empty.Add(itemType); - } - } - catch (Exception e) - { - // Probably a serialization exception - m_log.WarnFormat("ThrottleCheck: {0}", e.ToString()); - } - finally - { - Monitor.Pulse(this); - Monitor.Exit(this); - } - } - else - { - q.Enqueue(item); - } - } - - private static int ScaleThrottle(int value, int curmax, int newmax) - { - return (int)((value / (float)curmax) * newmax); - } - - public byte[] GetThrottlesPacked(float multiplier) - { - int singlefloat = 4; - float tResend = ResendThrottle.Throttle*multiplier; - float tLand = LandThrottle.Throttle*multiplier; - float tWind = WindThrottle.Throttle*multiplier; - float tCloud = CloudThrottle.Throttle*multiplier; - float tTask = TaskThrottle.Throttle*multiplier; - float tTexture = TextureThrottle.Throttle*multiplier; - float tAsset = AssetThrottle.Throttle*multiplier; - - byte[] throttles = new byte[singlefloat*7]; - int i = 0; - Buffer.BlockCopy(BitConverter.GetBytes(tResend), 0, throttles, singlefloat*i, singlefloat); - i++; - Buffer.BlockCopy(BitConverter.GetBytes(tLand), 0, throttles, singlefloat*i, singlefloat); - i++; - Buffer.BlockCopy(BitConverter.GetBytes(tWind), 0, throttles, singlefloat*i, singlefloat); - i++; - Buffer.BlockCopy(BitConverter.GetBytes(tCloud), 0, throttles, singlefloat*i, singlefloat); - i++; - Buffer.BlockCopy(BitConverter.GetBytes(tTask), 0, throttles, singlefloat*i, singlefloat); - i++; - Buffer.BlockCopy(BitConverter.GetBytes(tTexture), 0, throttles, singlefloat*i, singlefloat); - i++; - Buffer.BlockCopy(BitConverter.GetBytes(tAsset), 0, throttles, singlefloat*i, singlefloat); - - return throttles; - } - - public void SetThrottleFromClient(byte[] throttle) - { - // From mantis http://opensimulator.org/mantis/view.php?id=1374 - // it appears that sometimes we are receiving empty throttle byte arrays. - // TODO: Investigate this behaviour - if (throttle.Length == 0) - { - m_log.Warn("[PACKET QUEUE]: SetThrottleFromClient unexpectedly received a throttle byte array containing no elements!"); - return; - } - - int tResend = -1; - int tLand = -1; - int tWind = -1; - int tCloud = -1; - int tTask = -1; - int tTexture = -1; - int tAsset = -1; - int tall = -1; - int singlefloat = 4; - - //Agent Throttle Block contains 7 single floatingpoint values. - int j = 0; - - // Some Systems may be big endian... - // it might be smart to do this check more often... - if (!BitConverter.IsLittleEndian) - for (int i = 0; i < 7; i++) - Array.Reverse(throttle, j + i*singlefloat, singlefloat); - - // values gotten from OpenMetaverse.org/wiki/Throttle. Thanks MW_ - // bytes - // Convert to integer, since.. the full fp space isn't used. - tResend = (int) BitConverter.ToSingle(throttle, j); - j += singlefloat; - tLand = (int) BitConverter.ToSingle(throttle, j); - j += singlefloat; - tWind = (int) BitConverter.ToSingle(throttle, j); - j += singlefloat; - tCloud = (int) BitConverter.ToSingle(throttle, j); - j += singlefloat; - tTask = (int) BitConverter.ToSingle(throttle, j); - j += singlefloat; - tTexture = (int) BitConverter.ToSingle(throttle, j); - j += singlefloat; - tAsset = (int) BitConverter.ToSingle(throttle, j); - - tall = tResend + tLand + tWind + tCloud + tTask + tTexture + tAsset; - - /* - m_log.Info("[CLIENT]: Client AgentThrottle - Got throttle:resendbits=" + tResend + - " landbits=" + tLand + - " windbits=" + tWind + - " cloudbits=" + tCloud + - " taskbits=" + tTask + - " texturebits=" + tTexture + - " Assetbits=" + tAsset + - " Allbits=" + tall); - */ - - - // Total Sanity - // Make sure that the client sent sane total values. - - // If the client didn't send acceptable values.... - // Scale the clients values down until they are acceptable. - - if (tall <= TotalThrottle.Max) - { - ResendThrottle.Throttle = tResend; - LandThrottle.Throttle = tLand; - WindThrottle.Throttle = tWind; - CloudThrottle.Throttle = tCloud; - TaskThrottle.Throttle = tTask; - TextureThrottle.Throttle = tTexture; - AssetThrottle.Throttle = tAsset; - TotalThrottle.Throttle = tall; - } -// else if (tall < 1) -// { -// // client is stupid, penalize him by minning everything -// ResendThrottle.Throttle = ResendThrottle.Min; -// LandThrottle.Throttle = LandThrottle.Min; -// WindThrottle.Throttle = WindThrottle.Min; -// CloudThrottle.Throttle = CloudThrottle.Min; -// TaskThrottle.Throttle = TaskThrottle.Min; -// TextureThrottle.Throttle = TextureThrottle.Min; -// AssetThrottle.Throttle = AssetThrottle.Min; -// TotalThrottle.Throttle = TotalThrottle.Min; -// } - else - { - // we're over so figure out percentages and use those - ResendThrottle.Throttle = tResend; - - LandThrottle.Throttle = ScaleThrottle(tLand, tall, TotalThrottle.Max); - WindThrottle.Throttle = ScaleThrottle(tWind, tall, TotalThrottle.Max); - CloudThrottle.Throttle = ScaleThrottle(tCloud, tall, TotalThrottle.Max); - TaskThrottle.Throttle = ScaleThrottle(tTask, tall, TotalThrottle.Max); - TextureThrottle.Throttle = ScaleThrottle(tTexture, tall, TotalThrottle.Max); - AssetThrottle.Throttle = ScaleThrottle(tAsset, tall, TotalThrottle.Max); - TotalThrottle.Throttle = TotalThrottle.Max; - } - // effectively wiggling the slider causes things reset -// ResetCounters(); // DO NOT reset, better to send less for one period than more - } - - // See IPullStatsProvider - public string GetStats() - { - return string.Format("{0,7} {1,7} {2,7} {3,7} {4,7} {5,7} {6,7} {7,7} {8,7} {9,7}", - SendQueue.Count(), - IncomingPacketQueue.Count, - OutgoingPacketQueue.Count, - ResendOutgoingPacketQueue.Count, - LandOutgoingPacketQueue.Count, - WindOutgoingPacketQueue.Count, - CloudOutgoingPacketQueue.Count, - TaskOutgoingPacketQueue.Count, - TextureOutgoingPacketQueue.Count, - AssetOutgoingPacketQueue.Count); - } - - public LLQueItem[] GetQueueArray() - { - return SendQueue.GetQueueArray(); - } - - public float ThrottleMultiplier - { - get { return throttleMultiplier; } - } - - public int GetQueueCount(ThrottleOutPacketType queue) - { - switch (queue) - { - case ThrottleOutPacketType.Land: - return LandOutgoingPacketQueue.Count; - case ThrottleOutPacketType.Wind: - return WindOutgoingPacketQueue.Count; - case ThrottleOutPacketType.Cloud: - return CloudOutgoingPacketQueue.Count; - case ThrottleOutPacketType.Task: - return TaskOutgoingPacketQueue.Count; - case ThrottleOutPacketType.Texture: - return TextureOutgoingPacketQueue.Count; - case ThrottleOutPacketType.Asset: - return AssetOutgoingPacketQueue.Count; - } - - return 0; - } - } -} diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLPacketServer.cs b/OpenSim/Region/ClientStack/LindenUDP/LLPacketServer.cs deleted file mode 100644 index f08b7be..0000000 --- a/OpenSim/Region/ClientStack/LindenUDP/LLPacketServer.cs +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System.Net; -using System.Net.Sockets; -using OpenMetaverse; -using OpenMetaverse.Packets; -using OpenSim.Framework; - -namespace OpenSim.Region.ClientStack.LindenUDP -{ - /// - /// This class sets up new client stacks. It also handles the immediate distribution of incoming packets to - /// client stacks - /// - public class LLPacketServer - { -// private static readonly log4net.ILog m_log -// = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); - - protected readonly LLUDPServer m_networkHandler; - protected IScene m_scene; - - /// - /// Tweakable user settings - /// - private ClientStackUserSettings m_userSettings; - - public LLPacketServer(LLUDPServer networkHandler, ClientStackUserSettings userSettings) - { - m_userSettings = userSettings; - m_networkHandler = networkHandler; - - m_networkHandler.RegisterPacketServer(this); - } - - public IScene LocalScene - { - set { m_scene = value; } - } - - /// - /// Process an incoming packet. - /// - /// - /// - public virtual void InPacket(uint circuitCode, Packet packet) - { - m_scene.ClientManager.InPacket(circuitCode, packet); - } - - /// - /// Create a new client circuit - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - protected virtual IClientAPI CreateNewCircuit( - EndPoint remoteEP, IScene scene, - LLPacketServer packServer, AuthenticateResponse sessionInfo, - UUID agentId, UUID sessionId, uint circuitCode, EndPoint proxyEP) - { - return - new LLClientView( - remoteEP, scene, packServer, sessionInfo, agentId, sessionId, circuitCode, proxyEP, - m_userSettings); - } - - /// - /// Check whether a given client is authorized to connect. - /// - /// - /// - /// - /// - public virtual bool IsClientAuthorized( - UseCircuitCodePacket useCircuit, AgentCircuitManager circuitManager, out AuthenticateResponse sessionInfo) - { - UUID agentId = useCircuit.CircuitCode.ID; - UUID sessionId = useCircuit.CircuitCode.SessionID; - uint circuitCode = useCircuit.CircuitCode.Code; - - sessionInfo = circuitManager.AuthenticateSession(sessionId, agentId, circuitCode); - - if (!sessionInfo.Authorised) - return false; - - return true; - } - - /// - /// Add a new client circuit. We assume that is has already passed an authorization check - /// - /// - /// - /// - /// - /// - /// - /// true if a new circuit was created, false if a circuit with the given circuit code already existed - /// - public virtual bool AddNewClient( - EndPoint epSender, UseCircuitCodePacket useCircuit, - AuthenticateResponse sessionInfo, EndPoint proxyEP) - { - IClientAPI newuser; - uint circuitCode = useCircuit.CircuitCode.Code; - - if (m_scene.ClientManager.TryGetClient(circuitCode, out newuser)) - { - // The circuit is already known to the scene. This not actually a problem since this will currently - // occur if a client is crossing borders (hence upgrading its circuit). However, we shouldn't - // really by trying to add a new client if this is the case. - return false; - } - - UUID agentId = useCircuit.CircuitCode.ID; - UUID sessionId = useCircuit.CircuitCode.SessionID; - - newuser - = CreateNewCircuit( - epSender, m_scene, this, sessionInfo, agentId, sessionId, circuitCode, proxyEP); - - m_scene.ClientManager.Add(circuitCode, newuser); - - newuser.OnViewerEffect += m_scene.ClientManager.ViewerEffectHandler; - newuser.OnLogout += LogoutHandler; - newuser.OnConnectionClosed += CloseClient; - - newuser.Start(); - - return true; - } - - public void LogoutHandler(IClientAPI client) - { - client.SendLogoutPacket(); - CloseClient(client); - } - - /// - /// Send a packet to the given circuit - /// - /// - /// - /// - /// - public virtual void SendPacketTo(byte[] buffer, int size, SocketFlags flags, uint circuitcode) - { - m_networkHandler.SendPacketTo(buffer, size, flags, circuitcode); - } - - /// - /// Close a client circuit only - /// - /// - public virtual void CloseCircuit(uint circuitcode) - { - m_networkHandler.RemoveClientCircuit(circuitcode); - } - - /// - /// Completely close down the given client. - /// - /// - public virtual void CloseClient(IClientAPI client) - { - //m_log.Info("PacketServer:CloseClient()"); - - CloseCircuit(client.CircuitCode); - m_scene.ClientManager.Remove(client.CircuitCode); - client.Close(false); - } - } -} diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLQueItem.cs b/OpenSim/Region/ClientStack/LindenUDP/LLQueItem.cs deleted file mode 100644 index 0ed2bc1..0000000 --- a/OpenSim/Region/ClientStack/LindenUDP/LLQueItem.cs +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using OpenMetaverse.Packets; -using OpenSim.Framework; - -namespace OpenSim.Region.ClientStack.LindenUDP -{ - public class LLQueItem - { - public LLQueItem() - { - } - - public Packet Packet; - public bool Incoming; - public ThrottleOutPacketType throttleType; - public int TickCount; - public Object Identifier; - public int Resends; - public int Length; - public uint Sequence; - } -} diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs new file mode 100644 index 0000000..ad01135 --- /dev/null +++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs @@ -0,0 +1,370 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using OpenSim.Framework; +using OpenMetaverse; + +namespace OpenSim.Region.ClientStack.LindenUDP +{ + public delegate void QueueEmpty(ThrottleOutPacketType category); + + public class LLUDPClient + { + /// The number of packet categories to throttle on. If a throttle category is added + /// or removed, this number must also change + const int THROTTLE_CATEGORY_COUNT = 7; + + public event QueueEmpty OnQueueEmpty; + + /// AgentID for this client + public readonly UUID AgentID; + /// The remote address of the connected client + public readonly IPEndPoint RemoteEndPoint; + /// Circuit code that this client is connected on + public readonly uint CircuitCode; + /// Sequence numbers of packets we've received (for duplicate checking) + public readonly IncomingPacketHistoryCollection PacketArchive = new IncomingPacketHistoryCollection(200); + /// Packets we have sent that need to be ACKed by the client + public readonly UnackedPacketCollection NeedAcks = new UnackedPacketCollection(); + /// ACKs that are queued up, waiting to be sent to the client + public readonly LocklessQueue PendingAcks = new LocklessQueue(); + + /// Reference to the IClientAPI for this client + public LLClientView ClientAPI; + /// Current packet sequence number + public int CurrentSequence; + /// Current ping sequence number + public byte CurrentPingSequence; + /// True when this connection is alive, otherwise false + public bool IsConnected = true; + /// True when this connection is paused, otherwise false + public bool IsPaused = true; + /// Environment.TickCount when the last packet was received for this client + public int TickLastPacketReceived; + + /// Timer granularity. This is set to the measured resolution of Environment.TickCount + public readonly float G; + /// Smoothed round-trip time. A smoothed average of the round-trip time for sending a + /// reliable packet to the client and receiving an ACK + public float SRTT; + /// Round-trip time variance. Measures the consistency of round-trip times + public float RTTVAR; + /// Retransmission timeout. Packets that have not been acknowledged in this number of + /// milliseconds or longer will be resent + /// Calculated from and using the + /// guidelines in RFC 2988 + public int RTO; + /// Number of bytes received since the last acknowledgement was sent out. This is used + /// to loosely follow the TCP delayed ACK algorithm in RFC 1122 (4.2.3.2) + public int BytesSinceLastACK; + + /// Throttle bucket for this agent's connection + private readonly TokenBucket throttle; + /// Throttle buckets for each packet category + private readonly TokenBucket[] throttleCategories; + /// Throttle rate defaults and limits + private readonly ThrottleRates defaultThrottleRates; + /// Outgoing queues for throttled packets + private readonly LocklessQueue[] packetOutboxes = new LocklessQueue[THROTTLE_CATEGORY_COUNT]; + /// 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]; + /// A reference to the LLUDPServer that is managing this client + private readonly LLUDPServer udpServer; + + public LLUDPClient(LLUDPServer server, ThrottleRates rates, TokenBucket parentThrottle, uint circuitCode, UUID agentID, IPEndPoint remoteEndPoint) + { + udpServer = server; + AgentID = agentID; + RemoteEndPoint = remoteEndPoint; + CircuitCode = circuitCode; + defaultThrottleRates = rates; + + for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) + packetOutboxes[i] = new LocklessQueue(); + + throttle = new TokenBucket(parentThrottle, 0, 0); + 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); + throttleCategories[(int)ThrottleOutPacketType.Wind] = new TokenBucket(throttle, rates.WindLimit, rates.Wind); + throttleCategories[(int)ThrottleOutPacketType.Cloud] = new TokenBucket(throttle, rates.CloudLimit, rates.Cloud); + throttleCategories[(int)ThrottleOutPacketType.Task] = new TokenBucket(throttle, rates.TaskLimit, rates.Task); + throttleCategories[(int)ThrottleOutPacketType.Texture] = new TokenBucket(throttle, rates.TextureLimit, rates.Texture); + throttleCategories[(int)ThrottleOutPacketType.Asset] = new TokenBucket(throttle, rates.AssetLimit, rates.Asset); + + // Set the granularity variable used for retransmission calculations to + // the measured resolution of Environment.TickCount + G = server.TickCountResolution; + + // Default the retransmission timeout to three seconds + RTO = 3000; + } + + public void Shutdown() + { + IsConnected = false; + } + + public ClientInfo GetClientInfo() + { + // TODO: This data structure is wrong in so many ways + ClientInfo info = new ClientInfo(); + info.pendingAcks = new Dictionary(); + info.needAck = new Dictionary(); + + info.resendThrottle = throttleCategories[(int)ThrottleOutPacketType.Resend].DripRate; + info.landThrottle = throttleCategories[(int)ThrottleOutPacketType.Land].DripRate; + info.windThrottle = throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate; + info.cloudThrottle = throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate; + info.taskThrottle = throttleCategories[(int)ThrottleOutPacketType.Task].DripRate; + info.assetThrottle = throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate; + info.textureThrottle = throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate; + info.totalThrottle = info.resendThrottle + info.landThrottle + info.windThrottle + info.cloudThrottle + + info.taskThrottle + info.assetThrottle + info.textureThrottle; + + return info; + } + + public void SetClientInfo(ClientInfo info) + { + } + + public string GetStats() + { + return string.Format("{0,7} {1,7} {2,7} {3,7} {4,7} {5,7} {6,7} {7,7} {8,7} {9,7}", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0); + } + + public void SetThrottles(byte[] throttleData) + { + byte[] adjData; + int pos = 0; + + if (!BitConverter.IsLittleEndian) + { + byte[] newData = new byte[7 * 4]; + Buffer.BlockCopy(throttleData, 0, newData, 0, 7 * 4); + + for (int i = 0; i < 7; i++) + Array.Reverse(newData, i * 4, 4); + + adjData = newData; + } + else + { + adjData = throttleData; + } + + int resend = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; + int land = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; + int wind = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; + int cloud = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; + int task = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; + int texture = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; + int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); + + resend = (resend <= defaultThrottleRates.ResendLimit) ? resend : defaultThrottleRates.ResendLimit; + land = (land <= defaultThrottleRates.LandLimit) ? land : defaultThrottleRates.LandLimit; + wind = (wind <= defaultThrottleRates.WindLimit) ? wind : defaultThrottleRates.WindLimit; + cloud = (cloud <= defaultThrottleRates.CloudLimit) ? cloud : defaultThrottleRates.CloudLimit; + task = (task <= defaultThrottleRates.TaskLimit) ? task : defaultThrottleRates.TaskLimit; + texture = (texture <= defaultThrottleRates.TextureLimit) ? texture : defaultThrottleRates.TextureLimit; + asset = (asset <= defaultThrottleRates.AssetLimit) ? asset : defaultThrottleRates.AssetLimit; + + SetThrottle(ThrottleOutPacketType.Resend, resend); + SetThrottle(ThrottleOutPacketType.Land, land); + SetThrottle(ThrottleOutPacketType.Wind, wind); + SetThrottle(ThrottleOutPacketType.Cloud, cloud); + SetThrottle(ThrottleOutPacketType.Task, task); + SetThrottle(ThrottleOutPacketType.Texture, texture); + SetThrottle(ThrottleOutPacketType.Asset, asset); + } + + public byte[] GetThrottlesPacked() + { + byte[] data = new byte[7 * 4]; + int i = 0; + + Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Resend].DripRate), 0, data, i, 4); i += 4; + Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Land].DripRate), 0, data, i, 4); i += 4; + Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate), 0, data, i, 4); i += 4; + Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate), 0, data, i, 4); i += 4; + Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Task].DripRate), 0, data, i, 4); i += 4; + Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate), 0, data, i, 4); i += 4; + Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate), 0, data, i, 4); i += 4; + + return data; + } + + public void SetThrottle(ThrottleOutPacketType category, int rate) + { + int i = (int)category; + if (i >= 0 && i < throttleCategories.Length) + { + TokenBucket bucket = throttleCategories[(int)category]; + bucket.MaxBurst = rate; + bucket.DripRate = rate; + } + } + + public bool EnqueueOutgoing(OutgoingPacket packet) + { + int category = (int)packet.Category; + + if (category >= 0 && category < packetOutboxes.Length) + { + LocklessQueue queue = packetOutboxes[category]; + TokenBucket bucket = throttleCategories[category]; + + if (throttleCategories[category].RemoveTokens(packet.Buffer.DataLength)) + { + // Enough tokens were removed from the bucket, the packet will not be queued + return false; + } + else + { + // Not enough tokens in the bucket, queue this packet + queue.Enqueue(packet); + return true; + } + } + else + { + // We don't have a token bucket for this category, so it will not be queued + return false; + } + } + + /// + /// Loops through all of the packet queues for this client and tries to send + /// any outgoing packets, obeying the throttling bucket limits + /// + /// This function is only called from a synchronous loop in the + /// UDPServer so we don't need to bother making this thread safe + /// True if any packets were sent, otherwise false + public bool DequeueOutgoing() + { + OutgoingPacket packet; + LocklessQueue queue; + TokenBucket bucket; + bool packetSent = false; + + for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) + { + bucket = throttleCategories[i]; + + if (nextPackets[i] != null) + { + // 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])) + { + // Send the packet + udpServer.SendPacketFinal(nextPackets[i]); + nextPackets[i] = null; + packetSent = true; + } + } + else + { + // No dequeued packet waiting to be sent, try to pull one off + // this queue + queue = packetOutboxes[i]; + if (queue.Dequeue(out packet)) + { + // A packet was pulled off the queue. See if we have + // enough tokens in the bucket to send it out + if (bucket.RemoveTokens(packet.Buffer.DataLength)) + { + // Send the packet + udpServer.SendPacketFinal(packet); + packetSent = true; + } + else + { + // Save the dequeued packet and the length calculation for + // the next iteration + nextPackets[i] = packet; + nextPacketLengths[i] = packet.Buffer.DataLength; + } + } + else + { + // No packets in this queue. Fire the queue empty callback + QueueEmpty callback = OnQueueEmpty; + if (callback != null) + callback((ThrottleOutPacketType)i); + } + } + } + + return packetSent; + } + + public void UpdateRoundTrip(float r) + { + const float ALPHA = 0.125f; + const float BETA = 0.25f; + const float K = 4.0f; + + if (RTTVAR == 0.0f) + { + // First RTT measurement + SRTT = r; + RTTVAR = r * 0.5f; + } + else + { + // Subsequence RTT measurement + RTTVAR = (1.0f - BETA) * RTTVAR + BETA * Math.Abs(SRTT - r); + SRTT = (1.0f - ALPHA) * SRTT + ALPHA * r; + } + + // Always round retransmission timeout up to two seconds + RTO = Math.Max(2000, (int)(SRTT + Math.Max(G, K * RTTVAR))); + //Logger.Debug("Setting agent " + this.Agent.FullName + "'s RTO to " + RTO + "ms with an RTTVAR of " + + // RTTVAR + " based on new RTT of " + r + "ms"); + } + } +} diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPClientCollection.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPClientCollection.cs new file mode 100644 index 0000000..7d2da68 --- /dev/null +++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPClientCollection.cs @@ -0,0 +1,185 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using OpenSim.Framework; +using OpenMetaverse; + +using ReaderWriterLockImpl = OpenMetaverse.ReaderWriterLockSlim; + +namespace OpenSim.Region.ClientStack.LindenUDP +{ + public sealed class UDPClientCollection + { + Dictionary Dictionary1; + Dictionary Dictionary2; + LLUDPClient[] Array; + ReaderWriterLockImpl rwLock = new ReaderWriterLockImpl(); + + public UDPClientCollection() + { + Dictionary1 = new Dictionary(); + Dictionary2 = new Dictionary(); + Array = new LLUDPClient[0]; + } + + public UDPClientCollection(int capacity) + { + Dictionary1 = new Dictionary(capacity); + Dictionary2 = new Dictionary(capacity); + Array = new LLUDPClient[0]; + } + + public void Add(UUID key1, IPEndPoint key2, LLUDPClient value) + { + rwLock.EnterWriteLock(); + + try + { + if (Dictionary1.ContainsKey(key1)) + { + if (!Dictionary2.ContainsKey(key2)) + throw new ArgumentException("key1 exists in the dictionary but not key2"); + } + else if (Dictionary2.ContainsKey(key2)) + { + if (!Dictionary1.ContainsKey(key1)) + throw new ArgumentException("key2 exists in the dictionary but not key1"); + } + + Dictionary1[key1] = value; + Dictionary2[key2] = value; + + LLUDPClient[] oldArray = Array; + int oldLength = oldArray.Length; + + LLUDPClient[] newArray = new LLUDPClient[oldLength + 1]; + for (int i = 0; i < oldLength; i++) + newArray[i] = oldArray[i]; + newArray[oldLength] = value; + + Array = newArray; + } + finally { rwLock.ExitWriteLock(); } + } + + public bool Remove(UUID key1, IPEndPoint key2) + { + rwLock.EnterWriteLock(); + + try + { + LLUDPClient value; + if (Dictionary1.TryGetValue(key1, out value)) + { + Dictionary1.Remove(key1); + Dictionary2.Remove(key2); + + LLUDPClient[] oldArray = Array; + int oldLength = oldArray.Length; + + LLUDPClient[] newArray = new LLUDPClient[oldLength - 1]; + int j = 0; + for (int i = 0; i < oldLength; i++) + { + if (oldArray[i] != value) + newArray[j++] = oldArray[i]; + } + + Array = newArray; + return true; + } + } + finally { rwLock.ExitWriteLock(); } + + return false; + } + + public void Clear() + { + rwLock.EnterWriteLock(); + + try + { + Dictionary1.Clear(); + Dictionary2.Clear(); + Array = new LLUDPClient[0]; + } + finally { rwLock.ExitWriteLock(); } + } + + public int Count + { + get { return Array.Length; } + } + + public bool ContainsKey(UUID key) + { + return Dictionary1.ContainsKey(key); + } + + public bool ContainsKey(IPEndPoint key) + { + return Dictionary2.ContainsKey(key); + } + + public bool TryGetValue(UUID key, out LLUDPClient value) + { + bool success; + bool doLock = !rwLock.IsUpgradeableReadLockHeld; + if (doLock) rwLock.EnterReadLock(); + + try { success = Dictionary1.TryGetValue(key, out value); } + finally { if (doLock) rwLock.ExitReadLock(); } + + return success; + } + + public bool TryGetValue(IPEndPoint key, out LLUDPClient value) + { + bool success; + bool doLock = !rwLock.IsUpgradeableReadLockHeld; + if (doLock) rwLock.EnterReadLock(); + + try { success = Dictionary2.TryGetValue(key, out value); } + finally { if (doLock) rwLock.ExitReadLock(); } + + return success; + } + + public void ForEach(Action action) + { + bool doLock = !rwLock.IsUpgradeableReadLockHeld; + if (doLock) rwLock.EnterUpgradeableReadLock(); + + try { Parallel.ForEach(Array, action); } + finally { if (doLock) rwLock.ExitUpgradeableReadLock(); } + } + } +} diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs index 7964c50..348615e 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs @@ -35,6 +35,7 @@ using log4net; using Nini.Config; using OpenMetaverse.Packets; using OpenSim.Framework; +using OpenSim.Framework.Statistics; using OpenSim.Region.Framework.Scenes; using OpenMetaverse; @@ -190,31 +191,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } - public void RemoveClient(LLUDPClient udpClient) - { - IClientAPI client; - - if (m_scene.ClientManager.TryGetClient(udpClient.CircuitCode, out client)) - RemoveClient(client); - else - m_log.Warn("[LLUDPSERVER]: Failed to lookup IClientAPI for LLUDPClient " + udpClient.AgentID); - } - - public void SetClientPaused(UUID agentID, bool paused) - { - LLUDPClient client; - if (clients.TryGetValue(agentID, out client)) - { - client.IsPaused = paused; - } - else - { - m_log.Warn("[LLUDPSERVER]: Attempted to pause/unpause unknown agent " + agentID); - } - } - public void BroadcastPacket(Packet packet, ThrottleOutPacketType category, bool sendToPausedAgents, bool allowSplitting) { + // CoarseLocationUpdate packets cannot be split in an automated way + if (packet.Type == PacketType.CoarseLocationUpdate && allowSplitting) + allowSplitting = false; + if (allowSplitting && packet.HasVariableBlocks) { byte[][] datas = packet.ToBytesMultiple(); @@ -251,6 +233,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void SendPacket(LLUDPClient client, Packet packet, ThrottleOutPacketType category, bool allowSplitting) { + // CoarseLocationUpdate packets cannot be split in an automated way + if (packet.Type == PacketType.CoarseLocationUpdate && allowSplitting) + allowSplitting = false; + if (allowSplitting && packet.HasVariableBlocks) { byte[][] datas = packet.ToBytesMultiple(); @@ -339,6 +325,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } + public void SendPing(LLUDPClient client) + { + IClientAPI api = client.ClientAPI; + if (api != null) + api.SendStartPingCheck(client.CurrentPingSequence++); + } + public void ResendUnacked(LLUDPClient client) { if (client.NeedAcks.Count > 0) @@ -387,9 +380,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP //FIXME: Make 60 an .ini setting if (Environment.TickCount - client.TickLastPacketReceived > 1000 * 60) { - m_log.Warn("[LLUDPSERVER]: Ack timeout, disconnecting " + client.RemoteEndPoint); + m_log.Warn("[LLUDPSERVER]: Ack timeout, disconnecting " + client.ClientAPI.Name); - RemoveClient(client); + RemoveClient(client.ClientAPI); return; } } @@ -590,8 +583,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP LLUDPClient client = new LLUDPClient(this, m_throttleRates, m_throttle, circuitCode, agentID, remoteEndPoint); clients.Add(agentID, client.RemoteEndPoint, client); - // Create the IClientAPI - IClientAPI clientApi = new LLClientView(remoteEndPoint, m_scene, this, client, sessionInfo, agentID, sessionID, circuitCode); + // Create the LLClientView + LLClientView clientApi = new LLClientView(remoteEndPoint, m_scene, this, client, sessionInfo, agentID, sessionID, circuitCode); clientApi.OnViewerEffect += m_scene.ClientManager.ViewerEffectHandler; clientApi.OnLogout += LogoutHandler; clientApi.OnConnectionClosed += RemoveClient; @@ -618,23 +611,16 @@ namespace OpenSim.Region.ClientStack.LindenUDP private void IncomingPacketHandler() { - IncomingPacket incomingPacket = new IncomingPacket(); - Packet packet = null; - LLUDPClient client = null; + // Set this culture for the thread that incoming packets are received + // on to en-US to avoid number parsing issues + Culture.SetCurrentCulture(); + + IncomingPacket incomingPacket = default(IncomingPacket); while (base.IsRunning) { - // Reset packet to null for the check below - packet = null; - if (packetInbox.Dequeue(100, ref incomingPacket)) - { - packet = incomingPacket.Packet; - client = incomingPacket.Client; - - if (packet != null && client != null) - client.ClientAPI.ProcessInPacket(packet); - } + Util.FireAndForget(ProcessInPacket, incomingPacket); } if (packetInbox.Count > 0) @@ -642,32 +628,98 @@ namespace OpenSim.Region.ClientStack.LindenUDP packetInbox.Clear(); } + private void ProcessInPacket(object state) + { + IncomingPacket incomingPacket = (IncomingPacket)state; + Packet packet = incomingPacket.Packet; + LLUDPClient client = incomingPacket.Client; + + if (packet != null && client != null) + { + try + { + client.ClientAPI.ProcessInPacket(packet); + } + catch (ThreadAbortException) + { + throw; + } + catch (Exception e) + { + if (StatsManager.SimExtraStats != null) + StatsManager.SimExtraStats.AddAbnormalClientThreadTermination(); + + // Don't let a failure in an individual client thread crash the whole sim. + m_log.ErrorFormat("[LLUDPSERVER]: Client thread for {0} {1} crashed. Logging them out", client.ClientAPI.Name, client.AgentID); + m_log.Error(e.Message, e); + + try + { + // Make an attempt to alert the user that their session has crashed + AgentAlertMessagePacket alert = client.ClientAPI.BuildAgentAlertPacket( + "Unfortunately the session for this client on the server has crashed.\n" + + "Any further actions taken will not be processed.\n" + + "Please relog", true); + + SendPacket(client, alert, ThrottleOutPacketType.Unknown, false); + + // TODO: There may be a better way to do this. Perhaps kick? Not sure this propogates notifications to + // listeners yet, though. + client.ClientAPI.SendLogoutPacket(); + RemoveClient(client.ClientAPI); + } + catch (ThreadAbortException) + { + throw; + } + catch (Exception e2) + { + m_log.Error("[LLUDPSERVER]: Further exception thrown on forced session logout for " + client.ClientAPI.Name); + m_log.Error(e2.Message, e2); + } + } + } + } + private void OutgoingPacketHandler() { + // Set this culture for the thread that outgoing packets are sent + // on to en-US to avoid number parsing issues + Culture.SetCurrentCulture(); + int now = Environment.TickCount; int elapsedMS = 0; int elapsed100MS = 0; + int elapsed500MS = 0; while (base.IsRunning) { bool resendUnacked = false; bool sendAcks = false; + bool sendPings = false; bool packetSent = false; elapsedMS += Environment.TickCount - now; - // Check for packets that need to be resent every 100ms + // Check for pending outgoing resends every 100ms if (elapsedMS >= 100) { resendUnacked = true; elapsedMS -= 100; ++elapsed100MS; } - // Check for ACKs that need to be sent out every 500ms + // Check for pending outgoing ACKs every 500ms if (elapsed100MS >= 5) { sendAcks = true; elapsed100MS = 0; + ++elapsed500MS; + } + // Send pings to clients every 2000ms + if (elapsed500MS >= 4) + { + sendPings = true; + elapsed500MS = 0; } clients.ForEach( @@ -679,6 +731,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP ResendUnacked(client); if (sendAcks) SendAcks(client); + if (sendPings) + SendPing(client); } ); diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUtil.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUtil.cs deleted file mode 100644 index 066b19d..0000000 --- a/OpenSim/Region/ClientStack/LindenUDP/LLUtil.cs +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Net; -using OpenMetaverse; - -using ReaderWriterLockImpl = OpenMetaverse.ReaderWriterLockSlim; - -namespace OpenSim.Region.ClientStack.LindenUDP -{ - public class LLUtil - { - /// - /// Convert a string to bytes suitable for use in an LL UDP packet. - /// - /// Truncated to 254 characters if necessary - /// - public static byte[] StringToPacketBytes(string s) - { - // Anything more than 254 will cause libsecondlife to barf - // (libsl 1550) adds an \0 on the Utils.StringToBytes conversion if it isn't present - if (s.Length > 254) - { - s = s.Remove(254); - } - - return Utils.StringToBytes(s); - } - } -} diff --git a/OpenSim/Region/ClientStack/LindenUDP/OutgoingPacket.cs b/OpenSim/Region/ClientStack/LindenUDP/OutgoingPacket.cs new file mode 100644 index 0000000..69b0c5f --- /dev/null +++ b/OpenSim/Region/ClientStack/LindenUDP/OutgoingPacket.cs @@ -0,0 +1,56 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenSim.Framework; +using OpenMetaverse; + +namespace OpenSim.Region.ClientStack.LindenUDP +{ + public sealed class OutgoingPacket + { + /// Client this packet is destined for + public LLUDPClient Client; + /// Packet data to send + public UDPPacketBuffer Buffer; + /// Sequence number of the wrapped packet + public uint SequenceNumber; + /// Number of times this packet has been resent + public int ResendCount; + /// Environment.TickCount when this packet was last sent over the wire + public int TickCount; + /// Category this packet belongs to + public ThrottleOutPacketType Category; + + public OutgoingPacket(LLUDPClient client, UDPPacketBuffer buffer, ThrottleOutPacketType category) + { + Client = client; + Buffer = buffer; + Category = category; + } + } +} diff --git a/OpenSim/Region/ClientStack/LindenUDP/ThrottleRates.cs b/OpenSim/Region/ClientStack/LindenUDP/ThrottleRates.cs new file mode 100644 index 0000000..ffa20d5 --- /dev/null +++ b/OpenSim/Region/ClientStack/LindenUDP/ThrottleRates.cs @@ -0,0 +1,76 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using Nini.Config; + +namespace OpenSim.Region.ClientStack.LindenUDP +{ + public sealed class ThrottleRates + { + public int Resend; + public int Land; + public int Wind; + public int Cloud; + public int Task; + public int Texture; + public int Asset; + + public int ResendLimit; + public int LandLimit; + public int WindLimit; + public int CloudLimit; + public int TaskLimit; + public int TextureLimit; + public int AssetLimit; + + public ThrottleRates(IConfigSource config) + { + try + { + 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); + + 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); + } + catch (Exception) { } + } + } +} diff --git a/OpenSim/Region/ClientStack/LindenUDP/UnackedPacketCollection.cs b/OpenSim/Region/ClientStack/LindenUDP/UnackedPacketCollection.cs new file mode 100644 index 0000000..16c0035 --- /dev/null +++ b/OpenSim/Region/ClientStack/LindenUDP/UnackedPacketCollection.cs @@ -0,0 +1,114 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using OpenMetaverse; + +namespace OpenSim.Region.ClientStack.LindenUDP +{ + public sealed class UnackedPacketCollection + { + public object SyncRoot = new object(); + + SortedDictionary packets; + + public int Count { get { return packets.Count; } } + + public UnackedPacketCollection() + { + packets = new SortedDictionary(); + } + + public bool Add(OutgoingPacket packet) + { + lock (SyncRoot) + { + if (!packets.ContainsKey(packet.SequenceNumber)) + { + packets.Add(packet.SequenceNumber, packet); + return true; + } + return false; + } + } + + public bool RemoveUnsafe(uint sequenceNumber) + { + return packets.Remove(sequenceNumber); + } + + public bool RemoveUnsafe(uint sequenceNumber, out OutgoingPacket packet) + { + if (packets.TryGetValue(sequenceNumber, out packet)) + { + packets.Remove(sequenceNumber); + return true; + } + + return false; + } + + public OutgoingPacket GetOldest() + { + lock (SyncRoot) + { + using (SortedDictionary.ValueCollection.Enumerator e = packets.Values.GetEnumerator()) + return e.Current; + } + } + + public List GetExpiredPackets(int timeout) + { + List expiredPackets = null; + + lock (SyncRoot) + { + int now = Environment.TickCount; + foreach (OutgoingPacket packet in packets.Values) + { + if (packet.TickCount == 0) + continue; + + if (now - packet.TickCount >= timeout) + { + if (expiredPackets == null) + expiredPackets = new List(); + expiredPackets.Add(packet); + } + else + { + break; + } + } + } + + return expiredPackets; + } + } +} -- cgit v1.1