* 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 lineprioritization
parent
7ddb6fbced
commit
e7c877407f
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides helper methods for parallelizing loops
|
||||
/// </summary>
|
||||
public static class Parallel
|
||||
{
|
||||
private static readonly int processorCount = System.Environment.ProcessorCount;
|
||||
|
||||
/// <summary>
|
||||
/// Executes a for loop in which iterations may run in parallel
|
||||
/// </summary>
|
||||
/// <param name="fromInclusive">The loop will be started at this index</param>
|
||||
/// <param name="toExclusive">The loop will be terminated before this index is reached</param>
|
||||
/// <param name="body">Method body to run for each iteration of the loop</param>
|
||||
public static void For(int fromInclusive, int toExclusive, Action<int> body)
|
||||
{
|
||||
For(processorCount, fromInclusive, toExclusive, body);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a for loop in which iterations may run in parallel
|
||||
/// </summary>
|
||||
/// <param name="threadCount">The number of concurrent execution threads to run</param>
|
||||
/// <param name="fromInclusive">The loop will be started at this index</param>
|
||||
/// <param name="toExclusive">The loop will be terminated before this index is reached</param>
|
||||
/// <param name="body">Method body to run for each iteration of the loop</param>
|
||||
public static void For(int threadCount, int fromInclusive, int toExclusive, Action<int> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a foreach loop in which iterations may run in parallel
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Object type that the collection wraps</typeparam>
|
||||
/// <param name="enumerable">An enumerable collection to iterate over</param>
|
||||
/// <param name="body">Method body to run for each object in the collection</param>
|
||||
public static void ForEach<T>(IEnumerable<T> enumerable, Action<T> body)
|
||||
{
|
||||
ForEach<T>(processorCount, enumerable, body);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a foreach loop in which iterations may run in parallel
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Object type that the collection wraps</typeparam>
|
||||
/// <param name="threadCount">The number of concurrent execution threads to run</param>
|
||||
/// <param name="enumerable">An enumerable collection to iterate over</param>
|
||||
/// <param name="body">Method body to run for each object in the collection</param>
|
||||
public static void ForEach<T>(int threadCount, IEnumerable<T> enumerable, Action<T> body)
|
||||
{
|
||||
int counter = threadCount;
|
||||
AutoResetEvent threadFinishEvent = new AutoResetEvent(false);
|
||||
IEnumerator<T> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a series of tasks in parallel
|
||||
/// </summary>
|
||||
/// <param name="actions">A series of method bodies to execute</param>
|
||||
public static void Invoke(params Action[] actions)
|
||||
{
|
||||
Invoke(processorCount, actions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a series of tasks in parallel
|
||||
/// </summary>
|
||||
/// <param name="threadCount">The number of concurrent execution threads to run</param>
|
||||
/// <param name="actions">A series of method bodies to execute</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private static readonly long ThreadTimeout = 30 * 10000000;
|
||||
public static List<ThreadTrackerItem> m_Threads;
|
||||
public static Thread ThreadTrackerThread;
|
||||
|
||||
static ThreadTracker()
|
||||
public static ProcessThreadCollection GetThreads()
|
||||
{
|
||||
#if DEBUG
|
||||
m_Threads = new List<ThreadTrackerItem>();
|
||||
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<Thread> GetThreads()
|
||||
{
|
||||
if (m_Threads == null)
|
||||
return null;
|
||||
|
||||
List<Thread> threads = new List<Thread>();
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<byte>(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<byte>(ref data, 1024);
|
||||
data[1023] = 0;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
#region FireAndForget Threading Pattern
|
||||
|
||||
public static void FireAndForget(System.Threading.WaitCallback callback)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
|
@ -26,24 +26,17 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse.Packets;
|
||||
using OpenSim.Framework;
|
||||
using OpenMetaverse;
|
||||
using OpenMetaverse.Packets;
|
||||
|
||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
{
|
||||
public class LLQueItem
|
||||
public struct IncomingPacket
|
||||
{
|
||||
public LLQueItem()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Client this packet came from</summary>
|
||||
public LLUDPClient Client;
|
||||
/// <summary>Packet data that has been received</summary>
|
||||
public Packet Packet;
|
||||
public bool Incoming;
|
||||
public ThrottleOutPacketType throttleType;
|
||||
public int TickCount;
|
||||
public Object Identifier;
|
||||
public int Resends;
|
||||
public int Length;
|
||||
public uint Sequence;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
|
@ -25,47 +25,49 @@
|
|||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using OpenMetaverse.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// A circular buffer and hashset for tracking incoming packet sequence
|
||||
/// numbers
|
||||
/// </summary>
|
||||
class KillPacket : Packet
|
||||
public sealed class IncomingPacketHistoryCollection
|
||||
{
|
||||
public override int Length
|
||||
private readonly uint[] m_items;
|
||||
private HashSet<uint> m_hashSet;
|
||||
private int m_first;
|
||||
private int m_next;
|
||||
private int m_capacity;
|
||||
|
||||
public IncomingPacketHistoryCollection(int capacity)
|
||||
{
|
||||
get { return 0; }
|
||||
this.m_capacity = capacity;
|
||||
m_items = new uint[capacity];
|
||||
m_hashSet = new HashSet<uint>();
|
||||
}
|
||||
|
||||
public override void FromBytes(Header header, byte[] bytes, ref int i, ref int packetEnd)
|
||||
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;
|
||||
}
|
||||
|
||||
public override void FromBytes(byte[] bytes, ref int i, ref int packetEnd, byte[] zeroBuffer)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<string, UUID> m_defaultAnimations = new Dictionary<string, UUID>();
|
||||
private readonly IGroupsModule m_GroupsModule;
|
||||
|
||||
private int m_debugPacketLevel;
|
||||
private int m_cachedTextureSerial;
|
||||
private Timer m_clientPingTimer;
|
||||
private Timer m_avatarTerseUpdateTimer;
|
||||
private List<ImprovedTerseObjectUpdatePacket.ObjectDataBlock> m_avatarTerseUpdates = new List<ImprovedTerseObjectUpdatePacket.ObjectDataBlock>();
|
||||
private Timer m_primTerseUpdateTimer;
|
||||
private List<ImprovedTerseObjectUpdatePacket.ObjectDataBlock> m_primTerseUpdates = new List<ImprovedTerseObjectUpdatePacket.ObjectDataBlock>();
|
||||
private Timer m_primFullUpdateTimer;
|
||||
private List<ObjectUpdatePacket.ObjectDataBlock> m_primFullUpdates = new List<ObjectUpdatePacket.ObjectDataBlock>();
|
||||
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<IClientIM>(this);
|
||||
RegisterInterface<IClientChat>(this);
|
||||
RegisterInterface<IClientIPEndpoint>(this);
|
||||
m_GroupsModule = scene.RequestModuleInterface<IGroupsModule>();
|
||||
|
||||
m_moneyBalance = 1000;
|
||||
|
||||
m_channelVersion = Utils.StringToBytes(scene.GetSimulatorVersion());
|
||||
|
||||
InitDefaultAnimations();
|
||||
|
||||
m_scene = scene;
|
||||
//m_assetCache = assetCache;
|
||||
|
||||
m_assetService = m_scene.RequestModuleInterface<IAssetService>();
|
||||
|
||||
m_udpServer = udpServer;
|
||||
m_udpClient = udpClient;
|
||||
|
||||
m_GroupsModule = scene.RequestModuleInterface<IGroupsModule>();
|
||||
m_imageManager = new LLImageManager(this, m_assetService, Scene.RequestModuleInterface<IJ2KDecoder>());
|
||||
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<IJ2KDecoder>());
|
||||
}
|
||||
|
||||
public void SetDebugPacketLevel(int newDebugPacketLevel)
|
||||
public void SetDebugPacketLevel(int newDebug)
|
||||
{
|
||||
m_debugPacketLevel = newDebugPacketLevel;
|
||||
}
|
||||
|
||||
#region Client Methods
|
||||
|
||||
/// <summary>
|
||||
/// Close down the client view. This *must* be the last method called, since the last #
|
||||
/// statement of CloseCleanup() aborts the thread.
|
||||
/// </summary>
|
||||
/// <param name="shutdownCircuit"></param>
|
||||
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();
|
||||
|
@ -272,42 +278,20 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
|||
KillEndDone();
|
||||
}
|
||||
|
||||
Terminate();
|
||||
}
|
||||
IsActive = false;
|
||||
|
||||
/// <summary>
|
||||
/// Close down the client view. This *must* be the last method called, since the last #
|
||||
/// statement of CloseCleanup() aborts the thread.
|
||||
/// </summary>
|
||||
/// <param name="shutdownCircuit"></param>
|
||||
public void Close(bool shutdownCircuit)
|
||||
{
|
||||
m_clientPingTimer.Enabled = false;
|
||||
m_avatarTerseUpdateTimer.Close();
|
||||
m_primTerseUpdateTimer.Close();
|
||||
m_primFullUpdateTimer.Close();
|
||||
|
||||
m_log.DebugFormat(
|
||||
"[CLIENT]: Close has been called with shutdownCircuit = {0} for {1} attached to scene {2}",
|
||||
shutdownCircuit, Name, m_scene.RegionInfo.RegionName);
|
||||
//m_udpServer.OnPacketStats -= PopulateStats;
|
||||
m_udpClient.Shutdown();
|
||||
|
||||
if (m_imageManager != null)
|
||||
m_imageManager.Close();
|
||||
// wait for thread stoped
|
||||
// m_clientThread.Join();
|
||||
|
||||
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);
|
||||
}
|
||||
// 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
|
|||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -523,14 +479,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
|||
/// </summary>
|
||||
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
|
|||
/// <param name="message"></param>
|
||||
/// <param name="modal"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current throttles for this client as a packed byte array
|
||||
/// </summary>
|
||||
/// <param name="multiplier">Unused</param>
|
||||
/// <returns></returns>
|
||||
public byte[] GetThrottlesPacked(float multiplier)
|
||||
{
|
||||
return m_udpClient.GetThrottlesPacked();
|
||||
}
|
||||
|
||||
public bool IsThrottleEmpty(ThrottleOutPacketType category)
|
||||
{
|
||||
return m_udpClient.IsThrottleEmpty(category);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unused
|
||||
/// Cruft?
|
||||
/// </summary>
|
||||
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,16 +10159,16 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
|||
return info;
|
||||
}
|
||||
|
||||
public EndPoint GetClientEP()
|
||||
{
|
||||
return m_userEndPoint;
|
||||
}
|
||||
|
||||
public void SetClientInfo(ClientInfo info)
|
||||
{
|
||||
m_udpClient.SetClientInfo(info);
|
||||
}
|
||||
|
||||
public EndPoint GetClientEP()
|
||||
{
|
||||
return m_userEndPoint;
|
||||
}
|
||||
|
||||
#region Media Parcel Members
|
||||
|
||||
public void SendParcelMediaCommand(uint flags, ParcelMediaCommandEnum command, float time)
|
||||
|
|
|
@ -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<uint> m_PendingAcks = new List<uint>();
|
||||
private Dictionary<uint, uint> m_PendingAcksMap = new Dictionary<uint, uint>();
|
||||
|
||||
private Dictionary<uint, LLQueItem> m_NeedAck =
|
||||
new Dictionary<uint, LLQueItem>();
|
||||
|
||||
/// <summary>
|
||||
/// The number of milliseconds that can pass before a packet that needs an ack is resent.
|
||||
/// </param>
|
||||
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<uint> m_alreadySeenList = new List<uint>();
|
||||
private Dictionary<uint, int>m_alreadySeenTracker = new Dictionary<uint, int>();
|
||||
private int m_alreadySeenWindow = 30000;
|
||||
private int m_lastAlreadySeenCheck = Environment.TickCount & Int32.MaxValue;
|
||||
|
||||
// private Dictionary<uint, int> m_DupeTracker =
|
||||
// new Dictionary<uint, int>();
|
||||
// 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<PacketType> m_ImportantPackets = new List<PacketType>();
|
||||
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<LLQueItem> dropped = new List<LLQueItem>();
|
||||
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<uint, byte[]>();
|
||||
|
||||
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<uint>(m_PendingAcksMap.Keys);
|
||||
m_NeedAck = new Dictionary<uint, LLQueItem>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
/// <summary>
|
||||
/// Is queueing enabled at all?
|
||||
/// </summary>
|
||||
private bool m_enabled = true;
|
||||
|
||||
private OpenSim.Framework.BlockingQueue<LLQueItem> SendQueue;
|
||||
|
||||
private Queue<LLQueItem> IncomingPacketQueue;
|
||||
private Queue<LLQueItem> OutgoingPacketQueue;
|
||||
private Queue<LLQueItem> ResendOutgoingPacketQueue;
|
||||
private Queue<LLQueItem> LandOutgoingPacketQueue;
|
||||
private Queue<LLQueItem> WindOutgoingPacketQueue;
|
||||
private Queue<LLQueItem> CloudOutgoingPacketQueue;
|
||||
private Queue<LLQueItem> TaskOutgoingPacketQueue;
|
||||
private Queue<LLQueItem> TaskLowpriorityPacketQueue;
|
||||
private Queue<LLQueItem> TextureOutgoingPacketQueue;
|
||||
private Queue<LLQueItem> AssetOutgoingPacketQueue;
|
||||
|
||||
private List<ThrottleOutPacketType> Empty = new List<ThrottleOutPacketType>();
|
||||
// m_log.Info("[THROTTLE]: Entering Throttle");
|
||||
// private Dictionary<uint, uint> PendingAcks = new Dictionary<uint, uint>();
|
||||
// private Dictionary<uint, Packet> NeedAck = new Dictionary<uint, Packet>();
|
||||
|
||||
// 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<uint,int> contents = new Dictionary<uint, int>();
|
||||
|
||||
// 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<LLQueItem>();
|
||||
|
||||
IncomingPacketQueue = new Queue<LLQueItem>();
|
||||
OutgoingPacketQueue = new Queue<LLQueItem>();
|
||||
ResendOutgoingPacketQueue = new Queue<LLQueItem>();
|
||||
LandOutgoingPacketQueue = new Queue<LLQueItem>();
|
||||
WindOutgoingPacketQueue = new Queue<LLQueItem>();
|
||||
CloudOutgoingPacketQueue = new Queue<LLQueItem>();
|
||||
TaskOutgoingPacketQueue = new Queue<LLQueItem>();
|
||||
TaskLowpriorityPacketQueue = new Queue<LLQueItem>();
|
||||
TextureOutgoingPacketQueue = new Queue<LLQueItem>();
|
||||
AssetOutgoingPacketQueue = new Queue<LLQueItem>();
|
||||
|
||||
// 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<ThrottleOutPacketType> 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<ThrottleOutPacketType>(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<LLQueItem> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// This class sets up new client stacks. It also handles the immediate distribution of incoming packets to
|
||||
/// client stacks
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Tweakable user settings
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process an incoming packet.
|
||||
/// </summary>
|
||||
/// <param name="circuitCode"></param>
|
||||
/// <param name="packet"></param>
|
||||
public virtual void InPacket(uint circuitCode, Packet packet)
|
||||
{
|
||||
m_scene.ClientManager.InPacket(circuitCode, packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new client circuit
|
||||
/// </summary>
|
||||
/// <param name="remoteEP"></param>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="assetCache"></param>
|
||||
/// <param name="packServer"></param>
|
||||
/// <param name="sessionInfo"></param>
|
||||
/// <param name="agentId"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="circuitCode"></param>
|
||||
/// <param name="proxyEP"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether a given client is authorized to connect.
|
||||
/// </summary>
|
||||
/// <param name="useCircuit"></param>
|
||||
/// <param name="circuitManager"></param>
|
||||
/// <param name="sessionInfo"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new client circuit. We assume that is has already passed an authorization check
|
||||
/// </summary>
|
||||
/// <param name="epSender"></param>
|
||||
/// <param name="useCircuit"></param>
|
||||
/// <param name="assetCache"></param>
|
||||
/// <param name="sessionInfo"></param>
|
||||
/// <param name="proxyEP"></param>
|
||||
/// <returns>
|
||||
/// true if a new circuit was created, false if a circuit with the given circuit code already existed
|
||||
/// </returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a packet to the given circuit
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="flags"></param>
|
||||
/// <param name="circuitcode"></param>
|
||||
public virtual void SendPacketTo(byte[] buffer, int size, SocketFlags flags, uint circuitcode)
|
||||
{
|
||||
m_networkHandler.SendPacketTo(buffer, size, flags, circuitcode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close a client circuit only
|
||||
/// </summary>
|
||||
/// <param name="circuitcode"></param>
|
||||
public virtual void CloseCircuit(uint circuitcode)
|
||||
{
|
||||
m_networkHandler.RemoveClientCircuit(circuitcode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completely close down the given client.
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
public virtual void CloseClient(IClientAPI client)
|
||||
{
|
||||
//m_log.Info("PacketServer:CloseClient()");
|
||||
|
||||
CloseCircuit(client.CircuitCode);
|
||||
m_scene.ClientManager.Remove(client.CircuitCode);
|
||||
client.Close(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>The number of packet categories to throttle on. If a throttle category is added
|
||||
/// or removed, this number must also change</summary>
|
||||
const int THROTTLE_CATEGORY_COUNT = 7;
|
||||
|
||||
public event QueueEmpty OnQueueEmpty;
|
||||
|
||||
/// <summary>AgentID for this client</summary>
|
||||
public readonly UUID AgentID;
|
||||
/// <summary>The remote address of the connected client</summary>
|
||||
public readonly IPEndPoint RemoteEndPoint;
|
||||
/// <summary>Circuit code that this client is connected on</summary>
|
||||
public readonly uint CircuitCode;
|
||||
/// <summary>Sequence numbers of packets we've received (for duplicate checking)</summary>
|
||||
public readonly IncomingPacketHistoryCollection PacketArchive = new IncomingPacketHistoryCollection(200);
|
||||
/// <summary>Packets we have sent that need to be ACKed by the client</summary>
|
||||
public readonly UnackedPacketCollection NeedAcks = new UnackedPacketCollection();
|
||||
/// <summary>ACKs that are queued up, waiting to be sent to the client</summary>
|
||||
public readonly LocklessQueue<uint> PendingAcks = new LocklessQueue<uint>();
|
||||
|
||||
/// <summary>Reference to the IClientAPI for this client</summary>
|
||||
public LLClientView ClientAPI;
|
||||
/// <summary>Current packet sequence number</summary>
|
||||
public int CurrentSequence;
|
||||
/// <summary>Current ping sequence number</summary>
|
||||
public byte CurrentPingSequence;
|
||||
/// <summary>True when this connection is alive, otherwise false</summary>
|
||||
public bool IsConnected = true;
|
||||
/// <summary>True when this connection is paused, otherwise false</summary>
|
||||
public bool IsPaused = true;
|
||||
/// <summary>Environment.TickCount when the last packet was received for this client</summary>
|
||||
public int TickLastPacketReceived;
|
||||
|
||||
/// <summary>Timer granularity. This is set to the measured resolution of Environment.TickCount</summary>
|
||||
public readonly float G;
|
||||
/// <summary>Smoothed round-trip time. A smoothed average of the round-trip time for sending a
|
||||
/// reliable packet to the client and receiving an ACK</summary>
|
||||
public float SRTT;
|
||||
/// <summary>Round-trip time variance. Measures the consistency of round-trip times</summary>
|
||||
public float RTTVAR;
|
||||
/// <summary>Retransmission timeout. Packets that have not been acknowledged in this number of
|
||||
/// milliseconds or longer will be resent</summary>
|
||||
/// <remarks>Calculated from <seealso cref="SRTT"/> and <seealso cref="RTTVAR"/> using the
|
||||
/// guidelines in RFC 2988</remarks>
|
||||
public int RTO;
|
||||
/// <summary>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)</summary>
|
||||
public int BytesSinceLastACK;
|
||||
|
||||
/// <summary>Throttle bucket for this agent's connection</summary>
|
||||
private readonly TokenBucket throttle;
|
||||
/// <summary>Throttle buckets for each packet category</summary>
|
||||
private readonly TokenBucket[] throttleCategories;
|
||||
/// <summary>Throttle rate defaults and limits</summary>
|
||||
private readonly ThrottleRates defaultThrottleRates;
|
||||
/// <summary>Outgoing queues for throttled packets</summary>
|
||||
private readonly LocklessQueue<OutgoingPacket>[] packetOutboxes = new LocklessQueue<OutgoingPacket>[THROTTLE_CATEGORY_COUNT];
|
||||
/// <summary>A container that can hold one packet for each outbox, used to store
|
||||
/// dequeued packets that are being held for throttling</summary>
|
||||
private readonly OutgoingPacket[] nextPackets = new OutgoingPacket[THROTTLE_CATEGORY_COUNT];
|
||||
/// <summary>An optimization to store the length of dequeued packets being held
|
||||
/// for throttling. This avoids expensive calls to Packet.Length</summary>
|
||||
private readonly int[] nextPacketLengths = new int[THROTTLE_CATEGORY_COUNT];
|
||||
/// <summary>A reference to the LLUDPServer that is managing this client</summary>
|
||||
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<OutgoingPacket>();
|
||||
|
||||
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<uint, uint>();
|
||||
info.needAck = new Dictionary<uint, byte[]>();
|
||||
|
||||
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<OutgoingPacket> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loops through all of the packet queues for this client and tries to send
|
||||
/// any outgoing packets, obeying the throttling bucket limits
|
||||
/// </summary>
|
||||
/// <remarks>This function is only called from a synchronous loop in the
|
||||
/// UDPServer so we don't need to bother making this thread safe</remarks>
|
||||
/// <returns>True if any packets were sent, otherwise false</returns>
|
||||
public bool DequeueOutgoing()
|
||||
{
|
||||
OutgoingPacket packet;
|
||||
LocklessQueue<OutgoingPacket> 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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<UUID, LLUDPClient> Dictionary1;
|
||||
Dictionary<IPEndPoint, LLUDPClient> Dictionary2;
|
||||
LLUDPClient[] Array;
|
||||
ReaderWriterLockImpl rwLock = new ReaderWriterLockImpl();
|
||||
|
||||
public UDPClientCollection()
|
||||
{
|
||||
Dictionary1 = new Dictionary<UUID, LLUDPClient>();
|
||||
Dictionary2 = new Dictionary<IPEndPoint, LLUDPClient>();
|
||||
Array = new LLUDPClient[0];
|
||||
}
|
||||
|
||||
public UDPClientCollection(int capacity)
|
||||
{
|
||||
Dictionary1 = new Dictionary<UUID, LLUDPClient>(capacity);
|
||||
Dictionary2 = new Dictionary<IPEndPoint, LLUDPClient>(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<LLUDPClient> action)
|
||||
{
|
||||
bool doLock = !rwLock.IsUpgradeableReadLockHeld;
|
||||
if (doLock) rwLock.EnterUpgradeableReadLock();
|
||||
|
||||
try { Parallel.ForEach<LLUDPClient>(Array, action); }
|
||||
finally { if (doLock) rwLock.ExitUpgradeableReadLock(); }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
|
@ -26,31 +26,31 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using OpenSim.Framework;
|
||||
using OpenMetaverse;
|
||||
|
||||
using ReaderWriterLockImpl = OpenMetaverse.ReaderWriterLockSlim;
|
||||
|
||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
{
|
||||
public class LLUtil
|
||||
public sealed class OutgoingPacket
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert a string to bytes suitable for use in an LL UDP packet.
|
||||
/// </summary>
|
||||
/// <param name="s">Truncated to 254 characters if necessary</param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
/// <summary>Client this packet is destined for</summary>
|
||||
public LLUDPClient Client;
|
||||
/// <summary>Packet data to send</summary>
|
||||
public UDPPacketBuffer Buffer;
|
||||
/// <summary>Sequence number of the wrapped packet</summary>
|
||||
public uint SequenceNumber;
|
||||
/// <summary>Number of times this packet has been resent</summary>
|
||||
public int ResendCount;
|
||||
/// <summary>Environment.TickCount when this packet was last sent over the wire</summary>
|
||||
public int TickCount;
|
||||
/// <summary>Category this packet belongs to</summary>
|
||||
public ThrottleOutPacketType Category;
|
||||
|
||||
return Utils.StringToBytes(s);
|
||||
public OutgoingPacket(LLUDPClient client, UDPPacketBuffer buffer, ThrottleOutPacketType category)
|
||||
{
|
||||
Client = client;
|
||||
Buffer = buffer;
|
||||
Category = category;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) { }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<uint, OutgoingPacket> packets;
|
||||
|
||||
public int Count { get { return packets.Count; } }
|
||||
|
||||
public UnackedPacketCollection()
|
||||
{
|
||||
packets = new SortedDictionary<uint, OutgoingPacket>();
|
||||
}
|
||||
|
||||
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<uint, OutgoingPacket>.ValueCollection.Enumerator e = packets.Values.GetEnumerator())
|
||||
return e.Current;
|
||||
}
|
||||
}
|
||||
|
||||
public List<OutgoingPacket> GetExpiredPackets(int timeout)
|
||||
{
|
||||
List<OutgoingPacket> 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<OutgoingPacket>();
|
||||
expiredPackets.Add(packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return expiredPackets;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue