* Continued work on the new LLUDP implementation. Appears to be functioning, although not everything is reimplemented yet

* Replaced logic in ThreadTracker with a call to System.Diagnostics that does the same thing
* Added Util.StringToBytes256() and Util.StringToBytes1024() to clamp output at byte[256] and byte[1024], respectively
* Fixed formatting for a MySQLAssetData error logging line
prioritization
John Hurliman 2009-10-06 02:38:00 -07:00
parent 7ddb6fbced
commit e7c877407f
17 changed files with 1280 additions and 2244 deletions

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -26,138 +26,21 @@
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Diagnostics;
using log4net;
namespace OpenSim.Framework
{
public static class ThreadTracker
{
private static readonly ILog m_log
= LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly long ThreadTimeout = 30 * 10000000;
public static List<ThreadTrackerItem> m_Threads;
public static Thread ThreadTrackerThread;
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
static ThreadTracker()
public static ProcessThreadCollection GetThreads()
{
#if DEBUG
m_Threads = new List<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
}
}

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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();
@ -271,43 +277,21 @@ namespace OpenSim.Region.ClientStack.LindenUDP
// of the client thread regardless of where Close() is called.
KillEndDone();
}
Terminate();
}
/// <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;
IsActive = false;
m_log.DebugFormat(
"[CLIENT]: Close has been called with shutdownCircuit = {0} for {1} attached to scene {2}",
shutdownCircuit, Name, m_scene.RegionInfo.RegionName);
m_avatarTerseUpdateTimer.Close();
m_primTerseUpdateTimer.Close();
m_primFullUpdateTimer.Close();
if (m_imageManager != null)
m_imageManager.Close();
//m_udpServer.OnPacketStats -= PopulateStats;
m_udpClient.Shutdown();
if (m_udpServer != null)
m_udpServer.Flush();
// wait for thread stoped
// m_clientThread.Join();
// raise an event on the packet server to Shutdown the circuit
// Now, if we raise the event then the packet server will call this method itself, so don't try cleanup
// here otherwise we'll end up calling it twice.
// FIXME: In truth, I might be wrong but this whole business of calling this method twice (with different args) looks
// horribly tangly. Hopefully it should be possible to greatly simplify it.
if (shutdownCircuit)
{
if (OnConnectionClosed != null)
OnConnectionClosed(this);
}
else
{
CloseCleanup(shutdownCircuit);
}
// delete circuit code
//m_networkServer.CloseClient(this);
}
public void Kick(string message)
@ -329,10 +313,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
public void Stop()
{
// Shut down timers. Thread Context is Murky, lock all timers!
if (m_clientPingTimer.Enabled)
lock (m_clientPingTimer)
m_clientPingTimer.Stop();
if (m_avatarTerseUpdateTimer.Enabled)
lock (m_avatarTerseUpdateTimer)
m_avatarTerseUpdateTimer.Stop();
@ -346,25 +326,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
m_primFullUpdateTimer.Stop();
}
private void Terminate()
{
IsActive = false;
m_clientPingTimer.Close();
m_avatarTerseUpdateTimer.Close();
m_primTerseUpdateTimer.Close();
m_primFullUpdateTimer.Close();
//m_udpServer.OnPacketStats -= PopulateStats;
m_udpClient.Shutdown();
// wait for thread stoped
// m_clientThread.Join();
// delete circuit code
//m_networkServer.CloseClient(this);
}
#endregion Client Methods
#region Packet Handling
@ -452,7 +413,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
return result;
}
protected void DebugPacket(string direction, Packet packet)
/*protected void DebugPacket(string direction, Packet packet)
{
string info;
@ -478,7 +439,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
}
Console.WriteLine(m_circuitCode + ":" + direction + ": " + info);
}
}*/
#endregion Packet Handling
@ -490,8 +451,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
/// </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)

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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");
}
}
}

View File

@ -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(); }
}
}
}

View File

@ -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);
}
);

View File

@ -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)
/// <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;
public OutgoingPacket(LLUDPClient client, UDPPacketBuffer buffer, ThrottleOutPacketType category)
{
// Anything more than 254 will cause libsecondlife to barf
// (libsl 1550) adds an \0 on the Utils.StringToBytes conversion if it isn't present
if (s.Length > 254)
{
s = s.Remove(254);
}
return Utils.StringToBytes(s);
Client = client;
Buffer = buffer;
Category = category;
}
}
}

View File

@ -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) { }
}
}
}

View File

@ -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;
}
}
}