Merge branch 'htb-throttle'
commit
fe679be9e7
|
@ -89,7 +89,6 @@ namespace OpenSim.Client.MXP.PacketHandler
|
||||||
m_clientThread.Name = "MXPThread";
|
m_clientThread.Name = "MXPThread";
|
||||||
m_clientThread.IsBackground = true;
|
m_clientThread.IsBackground = true;
|
||||||
m_clientThread.Start();
|
m_clientThread.Start();
|
||||||
ThreadTracker.Add(m_clientThread);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartListener()
|
public void StartListener()
|
||||||
|
|
|
@ -239,10 +239,8 @@ namespace OpenSim.Data.MySQL
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
m_log.ErrorFormat(
|
m_log.ErrorFormat("[ASSET DB]: MySQL failure creating asset {0} with name \"{1}\". Attempting reconnect. Error: {2}",
|
||||||
"[ASSETS DB]: " +
|
asset.FullID, asset.Name, e.Message);
|
||||||
"MySql failure creating asset {0} with name {1}" + Environment.NewLine + e.ToString()
|
|
||||||
+ Environment.NewLine + "Attempting reconnection", asset.FullID, asset.Name);
|
|
||||||
_dbConnection.Reconnect();
|
_dbConnection.Reconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@ namespace OpenSim.Framework
|
||||||
public const bool DefaultUserServerHttpSSL = false;
|
public const bool DefaultUserServerHttpSSL = false;
|
||||||
public const uint DefaultMessageServerHttpPort = 8006;
|
public const uint DefaultMessageServerHttpPort = 8006;
|
||||||
public const bool DefaultMessageServerHttpSSL = false;
|
public const bool DefaultMessageServerHttpSSL = false;
|
||||||
public const uint DefaultGridServerHttpPort = 8003;
|
public const uint DefaultGridServerHttpPort = 8001;
|
||||||
public const uint DefaultInventoryServerHttpPort = 8003;
|
public const uint DefaultInventoryServerHttpPort = 8004;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,15 +56,15 @@ namespace OpenSim.Framework
|
||||||
m_configMember.addConfigurationOption("default_inventory_server",
|
m_configMember.addConfigurationOption("default_inventory_server",
|
||||||
ConfigurationOption.ConfigurationTypes.TYPE_STRING_NOT_EMPTY,
|
ConfigurationOption.ConfigurationTypes.TYPE_STRING_NOT_EMPTY,
|
||||||
"Default Inventory Server URI (this server's external name)",
|
"Default Inventory Server URI (this server's external name)",
|
||||||
"http://127.0.0.1:" + ConfigSettings.DefaultInventoryServerHttpPort, false);
|
"http://127.0.0.1:8004", false);
|
||||||
m_configMember.addConfigurationOption("default_user_server",
|
m_configMember.addConfigurationOption("default_user_server",
|
||||||
ConfigurationOption.ConfigurationTypes.TYPE_STRING_NOT_EMPTY,
|
ConfigurationOption.ConfigurationTypes.TYPE_STRING_NOT_EMPTY,
|
||||||
"Default User Server URI",
|
"Default User Server URI",
|
||||||
"http://127.0.0.1:" + ConfigSettings.DefaultUserServerHttpPort, false);
|
"http://127.0.0.1:8002", false);
|
||||||
m_configMember.addConfigurationOption("default_asset_server",
|
m_configMember.addConfigurationOption("default_asset_server",
|
||||||
ConfigurationOption.ConfigurationTypes.TYPE_STRING_NOT_EMPTY,
|
ConfigurationOption.ConfigurationTypes.TYPE_STRING_NOT_EMPTY,
|
||||||
"Default Asset Server URI",
|
"Default Asset Server URI",
|
||||||
"http://127.0.0.1:" + ConfigSettings.DefaultAssetServerHttpPort, false);
|
"http://127.0.0.1:8003", false);
|
||||||
m_configMember.addConfigurationOption("database_provider", ConfigurationOption.ConfigurationTypes.TYPE_STRING,
|
m_configMember.addConfigurationOption("database_provider", ConfigurationOption.ConfigurationTypes.TYPE_STRING,
|
||||||
"DLL for database provider", "OpenSim.Data.MySQL.dll", false);
|
"DLL for database provider", "OpenSim.Data.MySQL.dll", false);
|
||||||
m_configMember.addConfigurationOption("database_connect", ConfigurationOption.ConfigurationTypes.TYPE_STRING,
|
m_configMember.addConfigurationOption("database_connect", ConfigurationOption.ConfigurationTypes.TYPE_STRING,
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -109,9 +110,8 @@ namespace OpenSim.Framework.Servers
|
||||||
m_periodicDiagnosticsTimer.Elapsed += new ElapsedEventHandler(LogDiagnostics);
|
m_periodicDiagnosticsTimer.Elapsed += new ElapsedEventHandler(LogDiagnostics);
|
||||||
m_periodicDiagnosticsTimer.Enabled = true;
|
m_periodicDiagnosticsTimer.Enabled = true;
|
||||||
|
|
||||||
// Add ourselves to thread monitoring. This thread will go on to become the console listening thread
|
// This thread will go on to become the console listening thread
|
||||||
Thread.CurrentThread.Name = "ConsoleThread";
|
Thread.CurrentThread.Name = "ConsoleThread";
|
||||||
ThreadTracker.Add(Thread.CurrentThread);
|
|
||||||
|
|
||||||
ILoggerRepository repository = LogManager.GetRepository();
|
ILoggerRepository repository = LogManager.GetRepository();
|
||||||
IAppender[] appenders = repository.GetAppenders();
|
IAppender[] appenders = repository.GetAppenders();
|
||||||
|
@ -235,7 +235,7 @@ namespace OpenSim.Framework.Servers
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
List<Thread> threads = ThreadTracker.GetThreads();
|
ProcessThreadCollection threads = ThreadTracker.GetThreads();
|
||||||
if (threads == null)
|
if (threads == null)
|
||||||
{
|
{
|
||||||
sb.Append("OpenSim thread tracking is only enabled in DEBUG mode.");
|
sb.Append("OpenSim thread tracking is only enabled in DEBUG mode.");
|
||||||
|
@ -243,25 +243,15 @@ namespace OpenSim.Framework.Servers
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.Append(threads.Count + " threads are being tracked:" + Environment.NewLine);
|
sb.Append(threads.Count + " threads are being tracked:" + Environment.NewLine);
|
||||||
foreach (Thread t in threads)
|
foreach (ProcessThread t in threads)
|
||||||
{
|
{
|
||||||
if (t.IsAlive)
|
sb.Append("ID: " + t.Id + ", TotalProcessorTime: " + t.TotalProcessorTime + ", TimeRunning: " +
|
||||||
{
|
(DateTime.Now - t.StartTime) + ", Pri: " + t.CurrentPriority + ", State: " + t.ThreadState );
|
||||||
sb.Append(
|
if (t.ThreadState == System.Diagnostics.ThreadState.Wait)
|
||||||
"ID: " + t.ManagedThreadId + ", Name: " + t.Name + ", Alive: " + t.IsAlive
|
sb.Append(", Reason: " + t.WaitReason + Environment.NewLine);
|
||||||
+ ", Pri: " + t.Priority + ", State: " + t.ThreadState + Environment.NewLine);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
sb.Append(Environment.NewLine);
|
||||||
try
|
|
||||||
{
|
|
||||||
sb.Append("ID: " + t.ManagedThreadId + ", Name: " + t.Name + ", DEAD" + Environment.NewLine);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
sb.Append("THREAD ERROR" + Environment.NewLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int workers = 0, ports = 0, maxWorkers = 0, maxPorts = 0;
|
int workers = 0, ports = 0, maxWorkers = 0, maxPorts = 0;
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace OpenSim.Framework.Tests
|
||||||
[Test]
|
[Test]
|
||||||
public void DefaultThreadTrackerTest()
|
public void DefaultThreadTrackerTest()
|
||||||
{
|
{
|
||||||
List<Thread> lThread = ThreadTracker.GetThreads();
|
System.Diagnostics.ProcessThreadCollection lThread = ThreadTracker.GetThreads();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
foreach (Thread t in lThread)
|
foreach (Thread t in lThread)
|
||||||
|
@ -50,143 +50,7 @@ namespace OpenSim.Framework.Tests
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Assert.That(lThread.Count == 1);
|
Assert.That(lThread.Count > 0);
|
||||||
Assert.That(lThread[0].Name == "ThreadTrackerThread");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validate that adding a thread to the thread tracker works
|
|
||||||
/// Validate that removing a thread from the thread tracker also works.
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void AddThreadToThreadTrackerTestAndRemoveTest()
|
|
||||||
{
|
|
||||||
Thread t = new Thread(run);
|
|
||||||
t.Name = "TestThread";
|
|
||||||
t.Priority = ThreadPriority.BelowNormal;
|
|
||||||
t.IsBackground = true;
|
|
||||||
t.SetApartmentState(ApartmentState.MTA);
|
|
||||||
t.Start();
|
|
||||||
ThreadTracker.Add(t);
|
|
||||||
|
|
||||||
List<Thread> lThread = ThreadTracker.GetThreads();
|
|
||||||
|
|
||||||
Assert.That(lThread.Count == 2);
|
|
||||||
|
|
||||||
foreach (Thread tr in lThread)
|
|
||||||
{
|
|
||||||
Assert.That((tr.Name == "ThreadTrackerThread" || tr.Name == "TestThread"));
|
|
||||||
}
|
|
||||||
running = false;
|
|
||||||
ThreadTracker.Remove(t);
|
|
||||||
|
|
||||||
lThread = ThreadTracker.GetThreads();
|
|
||||||
|
|
||||||
Assert.That(lThread.Count == 1);
|
|
||||||
|
|
||||||
foreach (Thread tr in lThread)
|
|
||||||
{
|
|
||||||
Assert.That((tr.Name == "ThreadTrackerThread"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test a dead thread removal by aborting it and setting it's last seen active date to 50 seconds
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void DeadThreadTest()
|
|
||||||
{
|
|
||||||
Thread t = new Thread(run2);
|
|
||||||
t.Name = "TestThread";
|
|
||||||
t.Priority = ThreadPriority.BelowNormal;
|
|
||||||
t.IsBackground = true;
|
|
||||||
t.SetApartmentState(ApartmentState.MTA);
|
|
||||||
t.Start();
|
|
||||||
ThreadTracker.Add(t);
|
|
||||||
t.Abort();
|
|
||||||
Thread.Sleep(5000);
|
|
||||||
ThreadTracker.m_Threads[1].LastSeenActive = DateTime.Now.Ticks - (50*10000000);
|
|
||||||
ThreadTracker.CleanUp();
|
|
||||||
List<Thread> lThread = ThreadTracker.GetThreads();
|
|
||||||
|
|
||||||
Assert.That(lThread.Count == 1);
|
|
||||||
|
|
||||||
foreach (Thread tr in lThread)
|
|
||||||
{
|
|
||||||
Assert.That((tr.Name == "ThreadTrackerThread"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void UnstartedThreadTest()
|
|
||||||
{
|
|
||||||
Thread t = new Thread(run2);
|
|
||||||
t.Name = "TestThread";
|
|
||||||
t.Priority = ThreadPriority.BelowNormal;
|
|
||||||
t.IsBackground = true;
|
|
||||||
t.SetApartmentState(ApartmentState.MTA);
|
|
||||||
ThreadTracker.Add(t);
|
|
||||||
ThreadTracker.m_Threads[1].LastSeenActive = DateTime.Now.Ticks - (50 * 10000000);
|
|
||||||
ThreadTracker.CleanUp();
|
|
||||||
List<Thread> lThread = ThreadTracker.GetThreads();
|
|
||||||
|
|
||||||
Assert.That(lThread.Count == 1);
|
|
||||||
|
|
||||||
foreach (Thread tr in lThread)
|
|
||||||
{
|
|
||||||
Assert.That((tr.Name == "ThreadTrackerThread"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void NullThreadTest()
|
|
||||||
{
|
|
||||||
Thread t = null;
|
|
||||||
ThreadTracker.Add(t);
|
|
||||||
|
|
||||||
List<Thread> lThread = ThreadTracker.GetThreads();
|
|
||||||
|
|
||||||
Assert.That(lThread.Count == 1);
|
|
||||||
|
|
||||||
foreach (Thread tr in lThread)
|
|
||||||
{
|
|
||||||
Assert.That((tr.Name == "ThreadTrackerThread"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Worker thread 0
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="o"></param>
|
|
||||||
public void run(object o)
|
|
||||||
{
|
|
||||||
while (running)
|
|
||||||
{
|
|
||||||
Thread.Sleep(5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Worker thread 1
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="o"></param>
|
|
||||||
public void run2(object o)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (running2)
|
|
||||||
{
|
|
||||||
Thread.Sleep(5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (ThreadAbortException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,138 +26,21 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Diagnostics;
|
||||||
using log4net;
|
using log4net;
|
||||||
|
|
||||||
namespace OpenSim.Framework
|
namespace OpenSim.Framework
|
||||||
{
|
{
|
||||||
public static class ThreadTracker
|
public static class ThreadTracker
|
||||||
{
|
{
|
||||||
private static readonly ILog m_log
|
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||||
= LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
||||||
|
|
||||||
private static readonly long ThreadTimeout = 30 * 10000000;
|
|
||||||
public static List<ThreadTrackerItem> m_Threads;
|
|
||||||
public static Thread ThreadTrackerThread;
|
|
||||||
|
|
||||||
static ThreadTracker()
|
public static ProcessThreadCollection GetThreads()
|
||||||
{
|
{
|
||||||
#if DEBUG
|
Process thisProc = Process.GetCurrentProcess();
|
||||||
m_Threads = new List<ThreadTrackerItem>();
|
return thisProc.Threads;
|
||||||
ThreadTrackerThread = new Thread(ThreadTrackerThreadLoop);
|
|
||||||
ThreadTrackerThread.Name = "ThreadTrackerThread";
|
|
||||||
ThreadTrackerThread.IsBackground = true;
|
|
||||||
ThreadTrackerThread.Priority = ThreadPriority.BelowNormal;
|
|
||||||
ThreadTrackerThread.Start();
|
|
||||||
Add(ThreadTrackerThread);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,9 +29,9 @@ using System;
|
||||||
|
|
||||||
namespace OpenSim.Framework
|
namespace OpenSim.Framework
|
||||||
{
|
{
|
||||||
[Flags]
|
|
||||||
public enum ThrottleOutPacketType : int
|
public enum ThrottleOutPacketType : int
|
||||||
{
|
{
|
||||||
|
Unknown = -1, // Also doubles as 'do not throttle'
|
||||||
Resend = 0,
|
Resend = 0,
|
||||||
Land = 1,
|
Land = 1,
|
||||||
Wind = 2,
|
Wind = 2,
|
||||||
|
@ -39,11 +39,5 @@ namespace OpenSim.Framework
|
||||||
Task = 4,
|
Task = 4,
|
||||||
Texture = 5,
|
Texture = 5,
|
||||||
Asset = 6,
|
Asset = 6,
|
||||||
Unknown = 7, // Also doubles as 'do not throttle'
|
|
||||||
Back = 8,
|
|
||||||
|
|
||||||
TypeMask = 15, // The mask to mask off the flags
|
|
||||||
|
|
||||||
LowPriority = 128 // Additional flags
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1231,6 +1231,42 @@ namespace OpenSim.Framework
|
||||||
return (ipaddr1 != null) ? "http://" + ipaddr1.ToString() + ":" + port1 : uri;
|
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
|
#region FireAndForget Threading Pattern
|
||||||
|
|
||||||
public static void FireAndForget(System.Threading.WaitCallback callback)
|
public static void FireAndForget(System.Threading.WaitCallback callback)
|
||||||
|
@ -1247,7 +1283,9 @@ namespace OpenSim.Framework
|
||||||
{
|
{
|
||||||
System.Threading.WaitCallback callback = (System.Threading.WaitCallback)ar.AsyncState;
|
System.Threading.WaitCallback callback = (System.Threading.WaitCallback)ar.AsyncState;
|
||||||
|
|
||||||
callback.EndInvoke(ar);
|
try { callback.EndInvoke(ar); }
|
||||||
|
catch (Exception ex) { m_log.Error("[UTIL]: Asynchronous method threw an exception: " + ex.Message, ex); }
|
||||||
|
|
||||||
ar.AsyncWaitHandle.Close();
|
ar.AsyncWaitHandle.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,18 @@ namespace OpenSim
|
||||||
m_log.Info("[OPENSIM MAIN]: configured log4net using default OpenSim.exe.config");
|
m_log.Info("[OPENSIM MAIN]: configured log4net using default OpenSim.exe.config");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Increase the number of IOCP threads available. Mono defaults to a tragically low number
|
||||||
|
int workerThreads, iocpThreads;
|
||||||
|
System.Threading.ThreadPool.GetMaxThreads(out workerThreads, out iocpThreads);
|
||||||
|
m_log.InfoFormat("[OPENSIM MAIN]: Runtime gave us {0} worker threads and {1} IOCP threads", workerThreads, iocpThreads);
|
||||||
|
if (workerThreads < 500 || iocpThreads < 1000)
|
||||||
|
{
|
||||||
|
workerThreads = 500;
|
||||||
|
iocpThreads = 1000;
|
||||||
|
m_log.Info("[OPENSIM MAIN]: Bumping up to 500 worker threads and 1000 IOCP threads");
|
||||||
|
System.Threading.ThreadPool.SetMaxThreads(workerThreads, iocpThreads);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the system is compatible with OpenSimulator.
|
// Check if the system is compatible with OpenSimulator.
|
||||||
// Ensures that the minimum system requirements are met
|
// Ensures that the minimum system requirements are met
|
||||||
m_log.Info("Performing compatibility checks... ");
|
m_log.Info("Performing compatibility checks... ");
|
||||||
|
|
|
@ -675,7 +675,7 @@ namespace OpenSim
|
||||||
|
|
||||||
if (foundClientServer)
|
if (foundClientServer)
|
||||||
{
|
{
|
||||||
m_clientServers[clientServerElement].Server.Close();
|
m_clientServers[clientServerElement].NetworkStop();
|
||||||
m_clientServers.RemoveAt(clientServerElement);
|
m_clientServers.RemoveAt(clientServerElement);
|
||||||
}
|
}
|
||||||
IScene scene;
|
IScene scene;
|
||||||
|
|
|
@ -38,7 +38,7 @@ namespace OpenSim.Region.ClientStack
|
||||||
IPAddress _listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource,
|
IPAddress _listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource,
|
||||||
AgentCircuitManager authenticateClass);
|
AgentCircuitManager authenticateClass);
|
||||||
|
|
||||||
Socket Server { get; }
|
void NetworkStop();
|
||||||
bool HandlesRegion(Location x);
|
bool HandlesRegion(Location x);
|
||||||
void AddScene(IScene x);
|
void AddScene(IScene x);
|
||||||
|
|
||||||
|
|
|
@ -1,38 +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.Sockets;
|
|
||||||
|
|
||||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
|
||||||
{
|
|
||||||
public interface ILLClientStackNetworkHandler
|
|
||||||
{
|
|
||||||
void SendPacketTo(byte[] buffer, int size, SocketFlags flags, uint circuitcode); // EndPoint packetSender);
|
|
||||||
void RemoveClientCircuit(uint circuitcode);
|
|
||||||
void RegisterPacketServer(LLPacketServer server);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) Contributors, http://opensimulator.org/
|
|
||||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
* * Neither the name of the OpenSimulator Project nor the
|
|
||||||
* names of its contributors may be used to endorse or promote products
|
|
||||||
* derived from this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
|
||||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using OpenMetaverse;
|
|
||||||
using OpenMetaverse.Packets;
|
|
||||||
using OpenSim.Framework;
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface to a class that handles all the activity involved with maintaining the client circuit (handling acks,
|
|
||||||
/// resends, pings, etc.)
|
|
||||||
/// </summary>
|
|
||||||
public interface ILLPacketHandler : IDisposable
|
|
||||||
{
|
|
||||||
event PacketStats OnPacketStats;
|
|
||||||
event PacketDrop OnPacketDrop;
|
|
||||||
event QueueEmpty OnQueueEmpty;
|
|
||||||
SynchronizeClientHandler SynchronizeClient { set; }
|
|
||||||
|
|
||||||
int PacketsReceived { get; }
|
|
||||||
int PacketsReceivedReported { get; }
|
|
||||||
uint ResendTimeout { get; set; }
|
|
||||||
bool ReliableIsImportant { get; set; }
|
|
||||||
int MaxReliableResends { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initial handling of a received packet. It will be processed later in ProcessInPacket()
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="packet"></param>
|
|
||||||
void InPacket(Packet packet);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Take action depending on the type and contents of an received packet.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item"></param>
|
|
||||||
void ProcessInPacket(LLQueItem item);
|
|
||||||
|
|
||||||
void ProcessOutPacket(LLQueItem item);
|
|
||||||
void OutPacket(Packet NewPack,
|
|
||||||
ThrottleOutPacketType throttlePacketType);
|
|
||||||
void OutPacket(Packet NewPack,
|
|
||||||
ThrottleOutPacketType throttlePacketType, Object id);
|
|
||||||
LLPacketQueue PacketQueue { get; }
|
|
||||||
void Flush();
|
|
||||||
void Clear();
|
|
||||||
ClientInfo GetClientInfo();
|
|
||||||
void SetClientInfo(ClientInfo info);
|
|
||||||
void AddImportantPacket(PacketType type);
|
|
||||||
void RemoveImportantPacket(PacketType type);
|
|
||||||
int GetQueueCount(ThrottleOutPacketType queue);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) Contributors, http://opensimulator.org/
|
* Copyright (c) Contributors, http://opensimulator.org/
|
||||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||||
*
|
*
|
||||||
|
@ -26,24 +26,32 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using OpenMetaverse.Packets;
|
|
||||||
using OpenSim.Framework;
|
using OpenSim.Framework;
|
||||||
|
using OpenMetaverse;
|
||||||
|
using OpenMetaverse.Packets;
|
||||||
|
|
||||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
{
|
{
|
||||||
public class LLQueItem
|
/// <summary>
|
||||||
|
/// Holds a reference to a <seealso cref="LLUDPClient"/> and a <seealso cref="Packet"/>
|
||||||
|
/// for incoming packets
|
||||||
|
/// </summary>
|
||||||
|
public sealed class 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 Packet Packet;
|
||||||
public bool Incoming;
|
|
||||||
public ThrottleOutPacketType throttleType;
|
/// <summary>
|
||||||
public int TickCount;
|
/// Default constructor
|
||||||
public Object Identifier;
|
/// </summary>
|
||||||
public int Resends;
|
/// <param name="client">Reference to the client this packet came from</param>
|
||||||
public int Length;
|
/// <param name="packet">Packet data</param>
|
||||||
public uint Sequence;
|
public IncomingPacket(LLUDPClient client, Packet packet)
|
||||||
|
{
|
||||||
|
Client = client;
|
||||||
|
Packet = packet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) Contributors, http://opensimulator.org/
|
* Copyright (c) Contributors, http://opensimulator.org/
|
||||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||||
*
|
*
|
||||||
|
@ -25,47 +25,49 @@
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using OpenMetaverse.Packets;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When packetqueue dequeues this packet in the outgoing stream, it thread aborts
|
/// A circular buffer and hashset for tracking incoming packet sequence
|
||||||
/// Ensures that the thread abort happens from within the client thread
|
/// numbers
|
||||||
/// regardless of where the close method is called
|
|
||||||
/// </summary>
|
/// </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 false;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -76,27 +76,27 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
{
|
{
|
||||||
if (m_currentPacket <= m_stopPacket)
|
if (m_currentPacket <= m_stopPacket)
|
||||||
{
|
{
|
||||||
bool SendMore = true;
|
int count = 0;
|
||||||
|
bool sendMore = true;
|
||||||
|
|
||||||
if (!m_sentInfo || (m_currentPacket == 0))
|
if (!m_sentInfo || (m_currentPacket == 0))
|
||||||
{
|
{
|
||||||
if (SendFirstPacket(client))
|
sendMore = !SendFirstPacket(client);
|
||||||
{
|
|
||||||
SendMore = false;
|
|
||||||
}
|
|
||||||
m_sentInfo = true;
|
m_sentInfo = true;
|
||||||
m_currentPacket++;
|
++m_currentPacket;
|
||||||
|
++count;
|
||||||
}
|
}
|
||||||
if (m_currentPacket < 2)
|
if (m_currentPacket < 2)
|
||||||
{
|
{
|
||||||
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);
|
||||||
SendMore = SendPacket(client);
|
++m_currentPacket;
|
||||||
m_currentPacket++;
|
++count;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_currentPacket > m_stopPacket)
|
if (m_currentPacket > m_stopPacket)
|
||||||
|
@ -196,13 +196,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
|
|
||||||
m_currentPacket = StartPacket;
|
m_currentPacket = StartPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((m_imageManager != null) && (m_imageManager.Client != null) && (m_imageManager.Client.PacketHandler != null))
|
|
||||||
if (m_imageManager.Client.PacketHandler.GetQueueCount(ThrottleOutPacketType.Texture) == 0)
|
|
||||||
{
|
|
||||||
//m_log.Debug("No textures queued, sending one packet to kickstart it");
|
|
||||||
SendPacket(m_imageManager.Client);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -172,7 +172,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
m_lastloopprocessed = DateTime.Now.Ticks;
|
m_lastloopprocessed = DateTime.Now.Ticks;
|
||||||
|
|
||||||
// This can happen during Close()
|
// This can happen during Close()
|
||||||
if (m_client == null || m_client.PacketHandler == null || m_client.PacketHandler.PacketQueue == null)
|
if (m_client == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
while ((imagereq = GetHighestPriorityImage()) != null)
|
while ((imagereq = GetHighestPriorityImage()) != null)
|
||||||
|
|
|
@ -1,870 +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 class LLPacketHandler : ILLPacketHandler
|
|
||||||
{
|
|
||||||
private static readonly ILog m_log
|
|
||||||
= LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
||||||
|
|
||||||
//private int m_resentCount;
|
|
||||||
|
|
||||||
// Packet queues
|
|
||||||
//
|
|
||||||
LLPacketQueue m_PacketQueue;
|
|
||||||
|
|
||||||
public LLPacketQueue PacketQueue
|
|
||||||
{
|
|
||||||
get { return m_PacketQueue; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timer to run stats and acks on
|
|
||||||
//
|
|
||||||
private Timer m_AckTimer = new Timer(250);
|
|
||||||
|
|
||||||
// A list of the packets we haven't acked yet
|
|
||||||
//
|
|
||||||
private List<uint> m_PendingAcks = new List<uint>();
|
|
||||||
private Dictionary<uint, uint> m_PendingAcksMap = new Dictionary<uint, uint>();
|
|
||||||
|
|
||||||
private Dictionary<uint, LLQueItem> m_NeedAck =
|
|
||||||
new Dictionary<uint, LLQueItem>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The number of milliseconds that can pass before a packet that needs an ack is resent.
|
|
||||||
/// </param>
|
|
||||||
private uint m_ResendTimeout = 4000;
|
|
||||||
|
|
||||||
public uint ResendTimeout
|
|
||||||
{
|
|
||||||
get { return m_ResendTimeout; }
|
|
||||||
set { m_ResendTimeout = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private int m_MaxReliableResends = 3;
|
|
||||||
|
|
||||||
public int MaxReliableResends
|
|
||||||
{
|
|
||||||
get { return m_MaxReliableResends; }
|
|
||||||
set { m_MaxReliableResends = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track duplicated packets. This uses a Dictionary. Both insertion
|
|
||||||
// and lookup are common operations and need to take advantage of
|
|
||||||
// the hashing. Expiration is less common and can be allowed the
|
|
||||||
// time for a linear scan.
|
|
||||||
//
|
|
||||||
private List<uint> m_alreadySeenList = new List<uint>();
|
|
||||||
private Dictionary<uint, int>m_alreadySeenTracker = new Dictionary<uint, int>();
|
|
||||||
private int m_alreadySeenWindow = 30000;
|
|
||||||
private int m_lastAlreadySeenCheck = Environment.TickCount & Int32.MaxValue;
|
|
||||||
|
|
||||||
// private Dictionary<uint, int> m_DupeTracker =
|
|
||||||
// new Dictionary<uint, int>();
|
|
||||||
// private uint m_DupeTrackerWindow = 30;
|
|
||||||
// private int m_DupeTrackerLastCheck = Environment.TickCount;
|
|
||||||
|
|
||||||
// Values for the SimStatsReporter
|
|
||||||
//
|
|
||||||
private int m_PacketsReceived = 0;
|
|
||||||
private int m_PacketsReceivedReported = 0;
|
|
||||||
private int m_PacketsSent = 0;
|
|
||||||
private int m_PacketsSentReported = 0;
|
|
||||||
private int m_UnackedBytes = 0;
|
|
||||||
|
|
||||||
private int m_LastResend = 0;
|
|
||||||
|
|
||||||
public int PacketsReceived
|
|
||||||
{
|
|
||||||
get { return m_PacketsReceived; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int PacketsReceivedReported
|
|
||||||
{
|
|
||||||
get { return m_PacketsReceivedReported; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// The client we are working for
|
|
||||||
//
|
|
||||||
private IClientAPI m_Client;
|
|
||||||
|
|
||||||
// Some events
|
|
||||||
//
|
|
||||||
public event PacketStats OnPacketStats;
|
|
||||||
public event PacketDrop OnPacketDrop;
|
|
||||||
public event QueueEmpty OnQueueEmpty;
|
|
||||||
|
|
||||||
|
|
||||||
//private SynchronizeClientHandler m_SynchronizeClient = null;
|
|
||||||
|
|
||||||
public SynchronizeClientHandler SynchronizeClient
|
|
||||||
{
|
|
||||||
set { /* m_SynchronizeClient = value; */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packet sequencing
|
|
||||||
//
|
|
||||||
private uint m_Sequence = 0;
|
|
||||||
private object m_SequenceLock = new object();
|
|
||||||
private const int MAX_SEQUENCE = 0xFFFFFF;
|
|
||||||
|
|
||||||
// Packet dropping
|
|
||||||
//
|
|
||||||
List<PacketType> m_ImportantPackets = new List<PacketType>();
|
|
||||||
private bool m_ReliableIsImportant = false;
|
|
||||||
|
|
||||||
public bool ReliableIsImportant
|
|
||||||
{
|
|
||||||
get { return m_ReliableIsImportant; }
|
|
||||||
set { m_ReliableIsImportant = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private int m_DropSafeTimeout;
|
|
||||||
|
|
||||||
LLPacketServer m_PacketServer;
|
|
||||||
private byte[] m_ZeroOutBuffer = new byte[4096];
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Constructors
|
|
||||||
//
|
|
||||||
public LLPacketHandler(IClientAPI client, LLPacketServer server, ClientStackUserSettings userSettings)
|
|
||||||
{
|
|
||||||
m_Client = client;
|
|
||||||
m_PacketServer = server;
|
|
||||||
m_DropSafeTimeout = Environment.TickCount + 15000;
|
|
||||||
|
|
||||||
m_PacketQueue = new LLPacketQueue(client.AgentId, userSettings);
|
|
||||||
|
|
||||||
m_PacketQueue.OnQueueEmpty += TriggerOnQueueEmpty;
|
|
||||||
|
|
||||||
m_AckTimer.Elapsed += AckTimerElapsed;
|
|
||||||
m_AckTimer.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
m_AckTimer.Stop();
|
|
||||||
m_AckTimer.Close();
|
|
||||||
|
|
||||||
m_PacketQueue.Enqueue(null);
|
|
||||||
m_PacketQueue.Close();
|
|
||||||
m_Client = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send one packet. This actually doesn't send anything, it queues
|
|
||||||
// it. Designed to be fire-and-forget, but there is an optional
|
|
||||||
// notifier.
|
|
||||||
//
|
|
||||||
public void OutPacket(
|
|
||||||
Packet packet, ThrottleOutPacketType throttlePacketType)
|
|
||||||
{
|
|
||||||
OutPacket(packet, throttlePacketType, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OutPacket(
|
|
||||||
Packet packet, ThrottleOutPacketType throttlePacketType,
|
|
||||||
Object id)
|
|
||||||
{
|
|
||||||
// Call the load balancer's hook. If this is not active here
|
|
||||||
// we defer to the sim server this client is actually connected
|
|
||||||
// to. Packet drop notifies will not be triggered in this
|
|
||||||
// configuration!
|
|
||||||
//
|
|
||||||
|
|
||||||
packet.Header.Sequence = 0;
|
|
||||||
|
|
||||||
lock (m_NeedAck)
|
|
||||||
{
|
|
||||||
DropResend(id);
|
|
||||||
|
|
||||||
AddAcks(ref packet);
|
|
||||||
QueuePacket(packet, throttlePacketType, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddAcks(ref Packet packet)
|
|
||||||
{
|
|
||||||
// These packet types have shown to have issues with
|
|
||||||
// acks being appended to the payload, just don't send
|
|
||||||
// any with them until libsl is fixed.
|
|
||||||
//
|
|
||||||
if (packet is ViewerEffectPacket)
|
|
||||||
return;
|
|
||||||
if (packet is SimStatsPacket)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Add acks to outgoing packets
|
|
||||||
//
|
|
||||||
if (m_PendingAcks.Count > 0)
|
|
||||||
{
|
|
||||||
int count = m_PendingAcks.Count;
|
|
||||||
if (count > 10)
|
|
||||||
count = 10;
|
|
||||||
packet.Header.AckList = new uint[count];
|
|
||||||
packet.Header.AppendedAcks = true;
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
packet.Header.AckList[i] = m_PendingAcks[i];
|
|
||||||
m_PendingAcksMap.Remove(m_PendingAcks[i]);
|
|
||||||
}
|
|
||||||
m_PendingAcks.RemoveRange(0, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void QueuePacket(
|
|
||||||
Packet packet, ThrottleOutPacketType throttlePacketType,
|
|
||||||
Object id)
|
|
||||||
{
|
|
||||||
LLQueItem item = new LLQueItem();
|
|
||||||
item.Packet = packet;
|
|
||||||
item.Incoming = false;
|
|
||||||
item.throttleType = throttlePacketType;
|
|
||||||
item.TickCount = Environment.TickCount;
|
|
||||||
item.Identifier = id;
|
|
||||||
item.Resends = 0;
|
|
||||||
item.Length = packet.Length;
|
|
||||||
item.Sequence = packet.Header.Sequence;
|
|
||||||
|
|
||||||
m_PacketQueue.Enqueue(item);
|
|
||||||
m_PacketsSent++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResendUnacked()
|
|
||||||
{
|
|
||||||
int now = Environment.TickCount;
|
|
||||||
|
|
||||||
int intervalMs = 250;
|
|
||||||
|
|
||||||
if (m_LastResend != 0)
|
|
||||||
intervalMs = now - m_LastResend;
|
|
||||||
|
|
||||||
lock (m_NeedAck)
|
|
||||||
{
|
|
||||||
if (m_DropSafeTimeout > now ||
|
|
||||||
intervalMs > 500) // We were frozen!
|
|
||||||
{
|
|
||||||
foreach (LLQueItem data in m_NeedAck.Values)
|
|
||||||
{
|
|
||||||
if (m_DropSafeTimeout > now)
|
|
||||||
{
|
|
||||||
m_NeedAck[data.Packet.Header.Sequence].TickCount = now;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_NeedAck[data.Packet.Header.Sequence].TickCount += intervalMs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_LastResend = now;
|
|
||||||
|
|
||||||
// Unless we have received at least one ack, don't bother resending
|
|
||||||
// anything. There may not be a client there, don't clog up the
|
|
||||||
// pipes.
|
|
||||||
|
|
||||||
|
|
||||||
// Nothing to do
|
|
||||||
//
|
|
||||||
if (m_NeedAck.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int resent = 0;
|
|
||||||
long dueDate = now - m_ResendTimeout;
|
|
||||||
|
|
||||||
List<LLQueItem> dropped = new List<LLQueItem>();
|
|
||||||
foreach (LLQueItem data in m_NeedAck.Values)
|
|
||||||
{
|
|
||||||
Packet packet = data.Packet;
|
|
||||||
|
|
||||||
// Packets this old get resent
|
|
||||||
//
|
|
||||||
if (data.TickCount < dueDate && data.Sequence != 0 && !m_PacketQueue.Contains(data.Sequence))
|
|
||||||
{
|
|
||||||
if (resent < 20) // Was 20 (= Max 117kbit/sec resends)
|
|
||||||
{
|
|
||||||
m_NeedAck[packet.Header.Sequence].Resends++;
|
|
||||||
|
|
||||||
// The client needs to be told that a packet is being resent, otherwise it appears to believe
|
|
||||||
// that it should reset its sequence to that packet number.
|
|
||||||
packet.Header.Resent = true;
|
|
||||||
|
|
||||||
if ((m_NeedAck[packet.Header.Sequence].Resends >= m_MaxReliableResends) &&
|
|
||||||
(!m_ReliableIsImportant))
|
|
||||||
{
|
|
||||||
dropped.Add(data);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_NeedAck[packet.Header.Sequence].TickCount = Environment.TickCount;
|
|
||||||
QueuePacket(packet, ThrottleOutPacketType.Resend, data.Identifier);
|
|
||||||
resent++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_NeedAck[packet.Header.Sequence].TickCount += intervalMs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (LLQueItem data in dropped)
|
|
||||||
{
|
|
||||||
m_NeedAck.Remove(data.Packet.Header.Sequence);
|
|
||||||
TriggerOnPacketDrop(data.Packet, data.Identifier);
|
|
||||||
m_PacketQueue.Cancel(data.Packet.Header.Sequence);
|
|
||||||
PacketPool.Instance.ReturnPacket(data.Packet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the pending packet acks to the client
|
|
||||||
// Will send blocks of acks for up to 250 packets
|
|
||||||
//
|
|
||||||
private void SendAcks()
|
|
||||||
{
|
|
||||||
lock (m_NeedAck)
|
|
||||||
{
|
|
||||||
if (m_PendingAcks.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
PacketAckPacket acks = (PacketAckPacket)PacketPool.Instance.GetPacket(PacketType.PacketAck);
|
|
||||||
|
|
||||||
// The case of equality is more common than one might think,
|
|
||||||
// because this function will be called unconditionally when
|
|
||||||
// the counter reaches 250. So there is a good chance another
|
|
||||||
// packet with 250 blocks exists.
|
|
||||||
//
|
|
||||||
if (acks.Packets == null ||
|
|
||||||
acks.Packets.Length != m_PendingAcks.Count)
|
|
||||||
acks.Packets = new PacketAckPacket.PacketsBlock[m_PendingAcks.Count];
|
|
||||||
|
|
||||||
for (int i = 0; i < m_PendingAcks.Count; i++)
|
|
||||||
{
|
|
||||||
acks.Packets[i] = new PacketAckPacket.PacketsBlock();
|
|
||||||
acks.Packets[i].ID = m_PendingAcks[i];
|
|
||||||
|
|
||||||
}
|
|
||||||
m_PendingAcks.Clear();
|
|
||||||
m_PendingAcksMap.Clear();
|
|
||||||
|
|
||||||
acks.Header.Reliable = false;
|
|
||||||
OutPacket(acks, ThrottleOutPacketType.Unknown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue a packet ack. It will be sent either after 250 acks are
|
|
||||||
// queued, or when the timer fires.
|
|
||||||
//
|
|
||||||
private void AckPacket(Packet packet)
|
|
||||||
{
|
|
||||||
lock (m_NeedAck)
|
|
||||||
{
|
|
||||||
if (m_PendingAcks.Count < 250)
|
|
||||||
{
|
|
||||||
if (!m_PendingAcksMap.ContainsKey(packet.Header.Sequence))
|
|
||||||
{
|
|
||||||
m_PendingAcks.Add(packet.Header.Sequence);
|
|
||||||
m_PendingAcksMap.Add(packet.Header.Sequence,
|
|
||||||
packet.Header.Sequence);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SendAcks();
|
|
||||||
|
|
||||||
lock (m_NeedAck)
|
|
||||||
{
|
|
||||||
// If this is still full we have a truly exceptional
|
|
||||||
// condition (means, can't happen)
|
|
||||||
//
|
|
||||||
if (m_PendingAcks.Count < 250)
|
|
||||||
{
|
|
||||||
if (!m_PendingAcksMap.ContainsKey(packet.Header.Sequence))
|
|
||||||
{
|
|
||||||
m_PendingAcks.Add(packet.Header.Sequence);
|
|
||||||
m_PendingAcksMap.Add(packet.Header.Sequence,
|
|
||||||
packet.Header.Sequence);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the timer elapses, send the pending acks, trigger resends
|
|
||||||
// and report all the stats.
|
|
||||||
//
|
|
||||||
private void AckTimerElapsed(object sender, ElapsedEventArgs ea)
|
|
||||||
{
|
|
||||||
SendAcks();
|
|
||||||
ResendUnacked();
|
|
||||||
SendPacketStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push out pachet counts for the sim status reporter
|
|
||||||
//
|
|
||||||
private void SendPacketStats()
|
|
||||||
{
|
|
||||||
PacketStats handlerPacketStats = OnPacketStats;
|
|
||||||
if (handlerPacketStats != null)
|
|
||||||
{
|
|
||||||
handlerPacketStats(
|
|
||||||
m_PacketsReceived - m_PacketsReceivedReported,
|
|
||||||
m_PacketsSent - m_PacketsSentReported,
|
|
||||||
m_UnackedBytes);
|
|
||||||
|
|
||||||
m_PacketsReceivedReported = m_PacketsReceived;
|
|
||||||
m_PacketsSentReported = m_PacketsSent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't keep an unlimited record of dupes. This will prune the
|
|
||||||
// dictionary by age.
|
|
||||||
//
|
|
||||||
// NOTE: this needs to be called from within lock
|
|
||||||
// (m_alreadySeenTracker) context!
|
|
||||||
private void ExpireSeenPackets()
|
|
||||||
{
|
|
||||||
if (m_alreadySeenList.Count < 1024)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int ticks = 0;
|
|
||||||
int tc = Environment.TickCount & Int32.MaxValue;
|
|
||||||
if (tc >= m_lastAlreadySeenCheck)
|
|
||||||
ticks = tc - m_lastAlreadySeenCheck;
|
|
||||||
else
|
|
||||||
ticks = Int32.MaxValue - m_lastAlreadySeenCheck + tc;
|
|
||||||
|
|
||||||
if (ticks < 2000) return;
|
|
||||||
m_lastAlreadySeenCheck = tc;
|
|
||||||
|
|
||||||
// we calculate the drop dead tick count here instead of
|
|
||||||
// in the loop: any packet with a timestamp before
|
|
||||||
// dropDeadTC can be expired
|
|
||||||
int dropDeadTC = tc - m_alreadySeenWindow;
|
|
||||||
int i = 0;
|
|
||||||
while (i < m_alreadySeenList.Count && m_alreadySeenTracker[m_alreadySeenList[i]] < dropDeadTC)
|
|
||||||
{
|
|
||||||
m_alreadySeenTracker.Remove(m_alreadySeenList[i]);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
// if we dropped packet from m_alreadySeenTracker we need
|
|
||||||
// to drop them from m_alreadySeenList as well, let's do
|
|
||||||
// that in one go: the list is ordered after all.
|
|
||||||
if (i > 0)
|
|
||||||
{
|
|
||||||
m_alreadySeenList.RemoveRange(0, i);
|
|
||||||
// m_log.DebugFormat("[CLIENT]: expired {0} packets, {1}:{2} left", i, m_alreadySeenList.Count, m_alreadySeenTracker.Count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InPacket(Packet packet)
|
|
||||||
{
|
|
||||||
if (packet == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// When too many acks are needed to be sent, the client sends
|
|
||||||
// a packet consisting of acks only
|
|
||||||
//
|
|
||||||
if (packet.Type == PacketType.PacketAck)
|
|
||||||
{
|
|
||||||
PacketAckPacket ackPacket = (PacketAckPacket)packet;
|
|
||||||
|
|
||||||
foreach (PacketAckPacket.PacketsBlock block in ackPacket.Packets)
|
|
||||||
{
|
|
||||||
ProcessAck(block.ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
PacketPool.Instance.ReturnPacket(packet);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any packet can have some packet acks in the header.
|
|
||||||
// Process them here
|
|
||||||
//
|
|
||||||
if (packet.Header.AppendedAcks)
|
|
||||||
{
|
|
||||||
foreach (uint id in packet.Header.AckList)
|
|
||||||
{
|
|
||||||
ProcessAck(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this client is on another partial instance, no need
|
|
||||||
// to handle packets
|
|
||||||
//
|
|
||||||
if (!m_Client.IsActive && packet.Type != PacketType.LogoutRequest)
|
|
||||||
{
|
|
||||||
PacketPool.Instance.ReturnPacket(packet);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet.Type == PacketType.StartPingCheck)
|
|
||||||
{
|
|
||||||
StartPingCheckPacket startPing = (StartPingCheckPacket)packet;
|
|
||||||
CompletePingCheckPacket endPing
|
|
||||||
= (CompletePingCheckPacket)PacketPool.Instance.GetPacket(PacketType.CompletePingCheck);
|
|
||||||
|
|
||||||
endPing.PingID.PingID = startPing.PingID.PingID;
|
|
||||||
OutPacket(endPing, ThrottleOutPacketType.Task);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LLQueItem item = new LLQueItem();
|
|
||||||
item.Packet = packet;
|
|
||||||
item.Incoming = true;
|
|
||||||
m_PacketQueue.Enqueue(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ProcessInPacket(LLQueItem item)
|
|
||||||
{
|
|
||||||
Packet packet = item.Packet;
|
|
||||||
|
|
||||||
// Always ack the packet!
|
|
||||||
//
|
|
||||||
if (packet.Header.Reliable)
|
|
||||||
AckPacket(packet);
|
|
||||||
|
|
||||||
if (packet.Type != PacketType.AgentUpdate)
|
|
||||||
m_PacketsReceived++;
|
|
||||||
|
|
||||||
// Check for duplicate packets.. packets that the client is
|
|
||||||
// resending because it didn't receive our ack
|
|
||||||
//
|
|
||||||
lock (m_alreadySeenTracker)
|
|
||||||
{
|
|
||||||
ExpireSeenPackets();
|
|
||||||
|
|
||||||
if (m_alreadySeenTracker.ContainsKey(packet.Header.Sequence))
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_alreadySeenTracker.Add(packet.Header.Sequence, Environment.TickCount & Int32.MaxValue);
|
|
||||||
m_alreadySeenList.Add(packet.Header.Sequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Client.ProcessInPacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Flush()
|
|
||||||
{
|
|
||||||
m_PacketQueue.Flush();
|
|
||||||
m_UnackedBytes = (-1 * m_UnackedBytes);
|
|
||||||
SendPacketStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
m_UnackedBytes = (-1 * m_UnackedBytes);
|
|
||||||
SendPacketStats();
|
|
||||||
lock (m_NeedAck)
|
|
||||||
{
|
|
||||||
m_NeedAck.Clear();
|
|
||||||
m_PendingAcks.Clear();
|
|
||||||
m_PendingAcksMap.Clear();
|
|
||||||
}
|
|
||||||
m_Sequence += 1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProcessAck(uint id)
|
|
||||||
{
|
|
||||||
LLQueItem data;
|
|
||||||
|
|
||||||
lock (m_NeedAck)
|
|
||||||
{
|
|
||||||
//m_log.DebugFormat("[CLIENT]: In {0} received ack for packet {1}", m_Client.Scene.RegionInfo.ExternalEndPoint.Port, id);
|
|
||||||
|
|
||||||
if (!m_NeedAck.TryGetValue(id, out data))
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_NeedAck.Remove(id);
|
|
||||||
m_PacketQueue.Cancel(data.Sequence);
|
|
||||||
PacketPool.Instance.ReturnPacket(data.Packet);
|
|
||||||
m_UnackedBytes -= data.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate packet sequence numbers in a threadsave manner
|
|
||||||
//
|
|
||||||
protected uint NextPacketSequenceNumber()
|
|
||||||
{
|
|
||||||
// Set the sequence number
|
|
||||||
uint seq = 1;
|
|
||||||
lock (m_SequenceLock)
|
|
||||||
{
|
|
||||||
if (m_Sequence >= MAX_SEQUENCE)
|
|
||||||
{
|
|
||||||
m_Sequence = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_Sequence++;
|
|
||||||
}
|
|
||||||
seq = m_Sequence;
|
|
||||||
}
|
|
||||||
return seq;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientInfo GetClientInfo()
|
|
||||||
{
|
|
||||||
ClientInfo info = new ClientInfo();
|
|
||||||
|
|
||||||
info.pendingAcks = m_PendingAcksMap;
|
|
||||||
info.needAck = new Dictionary<uint, byte[]>();
|
|
||||||
|
|
||||||
lock (m_NeedAck)
|
|
||||||
{
|
|
||||||
foreach (uint key in m_NeedAck.Keys)
|
|
||||||
info.needAck.Add(key, m_NeedAck[key].Packet.ToBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
LLQueItem[] queitems = m_PacketQueue.GetQueueArray();
|
|
||||||
|
|
||||||
for (int i = 0; i < queitems.Length; i++)
|
|
||||||
{
|
|
||||||
if (queitems[i].Incoming == false)
|
|
||||||
info.out_packets.Add(queitems[i].Packet.ToBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
info.sequence = m_Sequence;
|
|
||||||
|
|
||||||
float multiplier = m_PacketQueue.ThrottleMultiplier;
|
|
||||||
info.resendThrottle = (int) (m_PacketQueue.ResendThrottle.Throttle / multiplier);
|
|
||||||
info.landThrottle = (int) (m_PacketQueue.LandThrottle.Throttle / multiplier);
|
|
||||||
info.windThrottle = (int) (m_PacketQueue.WindThrottle.Throttle / multiplier);
|
|
||||||
info.cloudThrottle = (int) (m_PacketQueue.CloudThrottle.Throttle / multiplier);
|
|
||||||
info.taskThrottle = (int) (m_PacketQueue.TaskThrottle.Throttle / multiplier);
|
|
||||||
info.assetThrottle = (int) (m_PacketQueue.AssetThrottle.Throttle / multiplier);
|
|
||||||
info.textureThrottle = (int) (m_PacketQueue.TextureThrottle.Throttle / multiplier);
|
|
||||||
info.totalThrottle = (int) (m_PacketQueue.TotalThrottle.Throttle / multiplier);
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetClientInfo(ClientInfo info)
|
|
||||||
{
|
|
||||||
m_PendingAcksMap = info.pendingAcks;
|
|
||||||
m_PendingAcks = new List<uint>(m_PendingAcksMap.Keys);
|
|
||||||
m_NeedAck = new Dictionary<uint, LLQueItem>();
|
|
||||||
|
|
||||||
Packet packet = null;
|
|
||||||
int packetEnd = 0;
|
|
||||||
byte[] zero = new byte[3000];
|
|
||||||
|
|
||||||
foreach (uint key in info.needAck.Keys)
|
|
||||||
{
|
|
||||||
byte[] buff = info.needAck[key];
|
|
||||||
packetEnd = buff.Length - 1;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
packet = PacketPool.Instance.GetPacket(buff, ref packetEnd, zero);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
LLQueItem item = new LLQueItem();
|
|
||||||
item.Packet = packet;
|
|
||||||
item.Incoming = false;
|
|
||||||
item.throttleType = 0;
|
|
||||||
item.TickCount = Environment.TickCount;
|
|
||||||
item.Identifier = 0;
|
|
||||||
item.Resends = 0;
|
|
||||||
item.Length = packet.Length;
|
|
||||||
item.Sequence = packet.Header.Sequence;
|
|
||||||
m_NeedAck.Add(key, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Sequence = info.sequence;
|
|
||||||
|
|
||||||
m_PacketQueue.ResendThrottle.Throttle = info.resendThrottle;
|
|
||||||
m_PacketQueue.LandThrottle.Throttle = info.landThrottle;
|
|
||||||
m_PacketQueue.WindThrottle.Throttle = info.windThrottle;
|
|
||||||
m_PacketQueue.CloudThrottle.Throttle = info.cloudThrottle;
|
|
||||||
m_PacketQueue.TaskThrottle.Throttle = info.taskThrottle;
|
|
||||||
m_PacketQueue.AssetThrottle.Throttle = info.assetThrottle;
|
|
||||||
m_PacketQueue.TextureThrottle.Throttle = info.textureThrottle;
|
|
||||||
m_PacketQueue.TotalThrottle.Throttle = info.totalThrottle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddImportantPacket(PacketType type)
|
|
||||||
{
|
|
||||||
if (m_ImportantPackets.Contains(type))
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_ImportantPackets.Add(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveImportantPacket(PacketType type)
|
|
||||||
{
|
|
||||||
if (!m_ImportantPackets.Contains(type))
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_ImportantPackets.Remove(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DropResend(Object id)
|
|
||||||
{
|
|
||||||
LLQueItem d = null;
|
|
||||||
|
|
||||||
foreach (LLQueItem data in m_NeedAck.Values)
|
|
||||||
{
|
|
||||||
if (data.Identifier != null && data.Identifier == id)
|
|
||||||
{
|
|
||||||
d = data;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null == d) return;
|
|
||||||
|
|
||||||
m_NeedAck.Remove(d.Packet.Header.Sequence);
|
|
||||||
m_PacketQueue.Cancel(d.Sequence);
|
|
||||||
PacketPool.Instance.ReturnPacket(d.Packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TriggerOnPacketDrop(Packet packet, Object id)
|
|
||||||
{
|
|
||||||
PacketDrop handlerPacketDrop = OnPacketDrop;
|
|
||||||
|
|
||||||
if (handlerPacketDrop == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
handlerPacketDrop(packet, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TriggerOnQueueEmpty(ThrottleOutPacketType queue)
|
|
||||||
{
|
|
||||||
QueueEmpty handlerQueueEmpty = OnQueueEmpty;
|
|
||||||
|
|
||||||
if (handlerQueueEmpty != null)
|
|
||||||
handlerQueueEmpty(queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the packet to bytes and stuff it onto the send queue
|
|
||||||
//
|
|
||||||
public void ProcessOutPacket(LLQueItem item)
|
|
||||||
{
|
|
||||||
Packet packet = item.Packet;
|
|
||||||
|
|
||||||
// Assign sequence number here to prevent out of order packets
|
|
||||||
if (packet.Header.Sequence == 0)
|
|
||||||
{
|
|
||||||
lock (m_NeedAck)
|
|
||||||
{
|
|
||||||
packet.Header.Sequence = NextPacketSequenceNumber();
|
|
||||||
item.Sequence = packet.Header.Sequence;
|
|
||||||
item.TickCount = Environment.TickCount;
|
|
||||||
|
|
||||||
// We want to see that packet arrive if it's reliable
|
|
||||||
if (packet.Header.Reliable)
|
|
||||||
{
|
|
||||||
m_UnackedBytes += item.Length;
|
|
||||||
|
|
||||||
// Keep track of when this packet was sent out
|
|
||||||
item.TickCount = Environment.TickCount;
|
|
||||||
|
|
||||||
m_NeedAck[packet.Header.Sequence] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we sent a killpacket
|
|
||||||
if (packet is KillPacket)
|
|
||||||
Abort();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// If this packet has been reused/returned, the ToBytes
|
|
||||||
// will blow up in our face.
|
|
||||||
// Fail gracefully.
|
|
||||||
//
|
|
||||||
|
|
||||||
// Actually make the byte array and send it
|
|
||||||
byte[] sendbuffer = item.Packet.ToBytes();
|
|
||||||
|
|
||||||
if (packet.Header.Zerocoded)
|
|
||||||
{
|
|
||||||
int packetsize = Helpers.ZeroEncode(sendbuffer,
|
|
||||||
sendbuffer.Length, m_ZeroOutBuffer);
|
|
||||||
m_PacketServer.SendPacketTo(m_ZeroOutBuffer, packetsize,
|
|
||||||
SocketFlags.None, m_Client.CircuitCode);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Need some extra space in case we need to add proxy
|
|
||||||
// information to the message later
|
|
||||||
Buffer.BlockCopy(sendbuffer, 0, m_ZeroOutBuffer, 0,
|
|
||||||
sendbuffer.Length);
|
|
||||||
m_PacketServer.SendPacketTo(m_ZeroOutBuffer,
|
|
||||||
sendbuffer.Length, SocketFlags.None, m_Client.CircuitCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (NullReferenceException)
|
|
||||||
{
|
|
||||||
m_log.Error("[PACKET]: Detected reuse of a returned packet");
|
|
||||||
m_PacketQueue.Cancel(item.Sequence);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a reliable packet, we are still holding a ref
|
|
||||||
// Dont't return in that case
|
|
||||||
//
|
|
||||||
if (!packet.Header.Reliable)
|
|
||||||
{
|
|
||||||
m_PacketQueue.Cancel(item.Sequence);
|
|
||||||
PacketPool.Instance.ReturnPacket(packet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Abort()
|
|
||||||
{
|
|
||||||
m_PacketQueue.Close();
|
|
||||||
Thread.CurrentThread.Abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetQueueCount(ThrottleOutPacketType queue)
|
|
||||||
{
|
|
||||||
return m_PacketQueue.GetQueueCount(queue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,742 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) Contributors, http://opensimulator.org/
|
|
||||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
* * Neither the name of the OpenSimulator Project nor the
|
|
||||||
* names of its contributors may be used to endorse or promote products
|
|
||||||
* derived from this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
|
||||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Timers;
|
|
||||||
using log4net;
|
|
||||||
using OpenMetaverse;
|
|
||||||
using OpenSim.Framework;
|
|
||||||
using OpenSim.Framework.Statistics;
|
|
||||||
using OpenSim.Framework.Statistics.Interfaces;
|
|
||||||
using Timer=System.Timers.Timer;
|
|
||||||
|
|
||||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
|
||||||
{
|
|
||||||
public class LLPacketQueue : IPullStatsProvider, IDisposable
|
|
||||||
{
|
|
||||||
private static readonly ILog m_log
|
|
||||||
= LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Is queueing enabled at all?
|
|
||||||
/// </summary>
|
|
||||||
private bool m_enabled = true;
|
|
||||||
|
|
||||||
private OpenSim.Framework.BlockingQueue<LLQueItem> SendQueue;
|
|
||||||
|
|
||||||
private Queue<LLQueItem> IncomingPacketQueue;
|
|
||||||
private Queue<LLQueItem> OutgoingPacketQueue;
|
|
||||||
private Queue<LLQueItem> ResendOutgoingPacketQueue;
|
|
||||||
private Queue<LLQueItem> LandOutgoingPacketQueue;
|
|
||||||
private Queue<LLQueItem> WindOutgoingPacketQueue;
|
|
||||||
private Queue<LLQueItem> CloudOutgoingPacketQueue;
|
|
||||||
private Queue<LLQueItem> TaskOutgoingPacketQueue;
|
|
||||||
private Queue<LLQueItem> TaskLowpriorityPacketQueue;
|
|
||||||
private Queue<LLQueItem> TextureOutgoingPacketQueue;
|
|
||||||
private Queue<LLQueItem> AssetOutgoingPacketQueue;
|
|
||||||
|
|
||||||
private List<ThrottleOutPacketType> Empty = new List<ThrottleOutPacketType>();
|
|
||||||
// m_log.Info("[THROTTLE]: Entering Throttle");
|
|
||||||
// private Dictionary<uint, uint> PendingAcks = new Dictionary<uint, uint>();
|
|
||||||
// private Dictionary<uint, Packet> NeedAck = new Dictionary<uint, Packet>();
|
|
||||||
|
|
||||||
// All throttle times and number of bytes are calculated by dividing by this value
|
|
||||||
// This value also determines how many times per throttletimems the timer will run
|
|
||||||
// If throttleimems is 1000 ms, then the timer will fire every 1000/7 milliseconds
|
|
||||||
|
|
||||||
private float throttleMultiplier = 2.0f; // Default value really doesn't matter.
|
|
||||||
private int throttleTimeDivisor = 7;
|
|
||||||
|
|
||||||
private int throttletimems = 1000;
|
|
||||||
|
|
||||||
internal LLPacketThrottle ResendThrottle;
|
|
||||||
internal LLPacketThrottle LandThrottle;
|
|
||||||
internal LLPacketThrottle WindThrottle;
|
|
||||||
internal LLPacketThrottle CloudThrottle;
|
|
||||||
internal LLPacketThrottle TaskThrottle;
|
|
||||||
internal LLPacketThrottle AssetThrottle;
|
|
||||||
internal LLPacketThrottle TextureThrottle;
|
|
||||||
internal LLPacketThrottle TotalThrottle;
|
|
||||||
|
|
||||||
private Dictionary<uint,int> contents = new Dictionary<uint, int>();
|
|
||||||
|
|
||||||
// private long LastThrottle;
|
|
||||||
// private long ThrottleInterval;
|
|
||||||
private Timer throttleTimer;
|
|
||||||
|
|
||||||
private UUID m_agentId;
|
|
||||||
|
|
||||||
public event QueueEmpty OnQueueEmpty;
|
|
||||||
|
|
||||||
public LLPacketQueue(UUID agentId, ClientStackUserSettings userSettings)
|
|
||||||
{
|
|
||||||
// While working on this, the BlockingQueue had me fooled for a bit.
|
|
||||||
// The Blocking queue causes the thread to stop until there's something
|
|
||||||
// in it to process. it's an on-purpose threadlock though because
|
|
||||||
// without it, the clientloop will suck up all sim resources.
|
|
||||||
|
|
||||||
SendQueue = new OpenSim.Framework.BlockingQueue<LLQueItem>();
|
|
||||||
|
|
||||||
IncomingPacketQueue = new Queue<LLQueItem>();
|
|
||||||
OutgoingPacketQueue = new Queue<LLQueItem>();
|
|
||||||
ResendOutgoingPacketQueue = new Queue<LLQueItem>();
|
|
||||||
LandOutgoingPacketQueue = new Queue<LLQueItem>();
|
|
||||||
WindOutgoingPacketQueue = new Queue<LLQueItem>();
|
|
||||||
CloudOutgoingPacketQueue = new Queue<LLQueItem>();
|
|
||||||
TaskOutgoingPacketQueue = new Queue<LLQueItem>();
|
|
||||||
TaskLowpriorityPacketQueue = new Queue<LLQueItem>();
|
|
||||||
TextureOutgoingPacketQueue = new Queue<LLQueItem>();
|
|
||||||
AssetOutgoingPacketQueue = new Queue<LLQueItem>();
|
|
||||||
|
|
||||||
// Store the throttle multiplier for posterity.
|
|
||||||
throttleMultiplier = userSettings.ClientThrottleMultipler;
|
|
||||||
|
|
||||||
|
|
||||||
int throttleMaxBPS = 1500000;
|
|
||||||
if (userSettings.TotalThrottleSettings != null)
|
|
||||||
throttleMaxBPS = userSettings.TotalThrottleSettings.Max;
|
|
||||||
|
|
||||||
// Set up the throttle classes (min, max, current) in bits per second
|
|
||||||
ResendThrottle = new LLPacketThrottle(5000, throttleMaxBPS / 15, 16000, userSettings.ClientThrottleMultipler);
|
|
||||||
LandThrottle = new LLPacketThrottle(1000, throttleMaxBPS / 15, 2000, userSettings.ClientThrottleMultipler);
|
|
||||||
WindThrottle = new LLPacketThrottle(0, throttleMaxBPS / 15, 0, userSettings.ClientThrottleMultipler);
|
|
||||||
CloudThrottle = new LLPacketThrottle(0, throttleMaxBPS / 15, 0, userSettings.ClientThrottleMultipler);
|
|
||||||
TaskThrottle = new LLPacketThrottle(1000, throttleMaxBPS / 2, 3000, userSettings.ClientThrottleMultipler);
|
|
||||||
AssetThrottle = new LLPacketThrottle(1000, throttleMaxBPS / 2, 1000, userSettings.ClientThrottleMultipler);
|
|
||||||
TextureThrottle = new LLPacketThrottle(1000, throttleMaxBPS / 2, 4000, userSettings.ClientThrottleMultipler);
|
|
||||||
|
|
||||||
|
|
||||||
// Total Throttle trumps all - it is the number of bits in total that are allowed to go out per second.
|
|
||||||
|
|
||||||
|
|
||||||
ThrottleSettings totalThrottleSettings = userSettings.TotalThrottleSettings;
|
|
||||||
if (null == totalThrottleSettings)
|
|
||||||
{
|
|
||||||
totalThrottleSettings = new ThrottleSettings(0, throttleMaxBPS, 28000);
|
|
||||||
}
|
|
||||||
|
|
||||||
TotalThrottle
|
|
||||||
= new LLPacketThrottle(
|
|
||||||
totalThrottleSettings.Min, totalThrottleSettings.Max, totalThrottleSettings.Current,
|
|
||||||
userSettings.ClientThrottleMultipler);
|
|
||||||
|
|
||||||
throttleTimer = new Timer((int)(throttletimems / throttleTimeDivisor));
|
|
||||||
throttleTimer.Elapsed += ThrottleTimerElapsed;
|
|
||||||
throttleTimer.Start();
|
|
||||||
|
|
||||||
// TIMERS needed for this
|
|
||||||
// LastThrottle = DateTime.Now.Ticks;
|
|
||||||
// ThrottleInterval = (long)(throttletimems/throttleTimeDivisor);
|
|
||||||
|
|
||||||
m_agentId = agentId;
|
|
||||||
|
|
||||||
if (StatsManager.SimExtraStats != null)
|
|
||||||
{
|
|
||||||
StatsManager.SimExtraStats.RegisterPacketQueueStatsProvider(m_agentId, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* STANDARD QUEUE MANIPULATION INTERFACES */
|
|
||||||
|
|
||||||
public void Enqueue(LLQueItem item)
|
|
||||||
{
|
|
||||||
if (!m_enabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// We could micro lock, but that will tend to actually
|
|
||||||
// probably be worse than just synchronizing on SendQueue
|
|
||||||
|
|
||||||
if (item == null)
|
|
||||||
{
|
|
||||||
SendQueue.Enqueue(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.Incoming)
|
|
||||||
{
|
|
||||||
SendQueue.PriorityEnqueue(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.Sequence != 0)
|
|
||||||
lock (contents)
|
|
||||||
{
|
|
||||||
if (contents.ContainsKey(item.Sequence))
|
|
||||||
contents[item.Sequence] += 1;
|
|
||||||
else
|
|
||||||
contents.Add(item.Sequence, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
switch (item.throttleType & ThrottleOutPacketType.TypeMask)
|
|
||||||
{
|
|
||||||
case ThrottleOutPacketType.Resend:
|
|
||||||
ThrottleCheck(ref ResendThrottle, ref ResendOutgoingPacketQueue, item, ThrottleOutPacketType.Resend);
|
|
||||||
break;
|
|
||||||
case ThrottleOutPacketType.Texture:
|
|
||||||
ThrottleCheck(ref TextureThrottle, ref TextureOutgoingPacketQueue, item, ThrottleOutPacketType.Texture);
|
|
||||||
break;
|
|
||||||
case ThrottleOutPacketType.Task:
|
|
||||||
if ((item.throttleType & ThrottleOutPacketType.LowPriority) != 0)
|
|
||||||
ThrottleCheck(ref TaskThrottle, ref TaskLowpriorityPacketQueue, item, ThrottleOutPacketType.Task);
|
|
||||||
else
|
|
||||||
ThrottleCheck(ref TaskThrottle, ref TaskOutgoingPacketQueue, item, ThrottleOutPacketType.Task);
|
|
||||||
break;
|
|
||||||
case ThrottleOutPacketType.Land:
|
|
||||||
ThrottleCheck(ref LandThrottle, ref LandOutgoingPacketQueue, item, ThrottleOutPacketType.Land);
|
|
||||||
break;
|
|
||||||
case ThrottleOutPacketType.Asset:
|
|
||||||
ThrottleCheck(ref AssetThrottle, ref AssetOutgoingPacketQueue, item, ThrottleOutPacketType.Asset);
|
|
||||||
break;
|
|
||||||
case ThrottleOutPacketType.Cloud:
|
|
||||||
ThrottleCheck(ref CloudThrottle, ref CloudOutgoingPacketQueue, item, ThrottleOutPacketType.Cloud);
|
|
||||||
break;
|
|
||||||
case ThrottleOutPacketType.Wind:
|
|
||||||
ThrottleCheck(ref WindThrottle, ref WindOutgoingPacketQueue, item, ThrottleOutPacketType.Wind);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Acknowledgements and other such stuff should go directly to the blocking Queue
|
|
||||||
// Throttling them may and likely 'will' be problematic
|
|
||||||
SendQueue.PriorityEnqueue(item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public LLQueItem Dequeue()
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
LLQueItem item = SendQueue.Dequeue();
|
|
||||||
if (item == null)
|
|
||||||
return null;
|
|
||||||
if (item.Incoming)
|
|
||||||
return item;
|
|
||||||
item.TickCount = System.Environment.TickCount;
|
|
||||||
if (item.Sequence == 0)
|
|
||||||
return item;
|
|
||||||
lock (contents)
|
|
||||||
{
|
|
||||||
if (contents.ContainsKey(item.Sequence))
|
|
||||||
{
|
|
||||||
if (contents[item.Sequence] == 1)
|
|
||||||
contents.Remove(item.Sequence);
|
|
||||||
else
|
|
||||||
contents[item.Sequence] -= 1;
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Cancel(uint sequence)
|
|
||||||
{
|
|
||||||
lock (contents) contents.Remove(sequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(uint sequence)
|
|
||||||
{
|
|
||||||
lock (contents) return contents.ContainsKey(sequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Flush()
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
// These categories do not contain transactional packets so we can safely drop any pending data in them
|
|
||||||
LandOutgoingPacketQueue.Clear();
|
|
||||||
WindOutgoingPacketQueue.Clear();
|
|
||||||
CloudOutgoingPacketQueue.Clear();
|
|
||||||
TextureOutgoingPacketQueue.Clear();
|
|
||||||
AssetOutgoingPacketQueue.Clear();
|
|
||||||
|
|
||||||
// Now comes the fun part.. we dump all remaining resend and task packets into the send queue
|
|
||||||
while (ResendOutgoingPacketQueue.Count > 0 || TaskOutgoingPacketQueue.Count > 0 || TaskLowpriorityPacketQueue.Count > 0)
|
|
||||||
{
|
|
||||||
if (ResendOutgoingPacketQueue.Count > 0)
|
|
||||||
SendQueue.Enqueue(ResendOutgoingPacketQueue.Dequeue());
|
|
||||||
|
|
||||||
if (TaskOutgoingPacketQueue.Count > 0)
|
|
||||||
SendQueue.PriorityEnqueue(TaskOutgoingPacketQueue.Dequeue());
|
|
||||||
|
|
||||||
if (TaskLowpriorityPacketQueue.Count > 0)
|
|
||||||
SendQueue.Enqueue(TaskLowpriorityPacketQueue.Dequeue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WipeClean()
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
ResendOutgoingPacketQueue.Clear();
|
|
||||||
LandOutgoingPacketQueue.Clear();
|
|
||||||
WindOutgoingPacketQueue.Clear();
|
|
||||||
CloudOutgoingPacketQueue.Clear();
|
|
||||||
TaskOutgoingPacketQueue.Clear();
|
|
||||||
TaskLowpriorityPacketQueue.Clear();
|
|
||||||
TextureOutgoingPacketQueue.Clear();
|
|
||||||
AssetOutgoingPacketQueue.Clear();
|
|
||||||
SendQueue.Clear();
|
|
||||||
lock (contents) contents.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Close()
|
|
||||||
{
|
|
||||||
Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Flush();
|
|
||||||
WipeClean(); // I'm sure there's a dirty joke in here somewhere. -AFrisby
|
|
||||||
|
|
||||||
m_enabled = false;
|
|
||||||
throttleTimer.Stop();
|
|
||||||
throttleTimer.Close();
|
|
||||||
|
|
||||||
if (StatsManager.SimExtraStats != null)
|
|
||||||
{
|
|
||||||
StatsManager.SimExtraStats.DeregisterPacketQueueStatsProvider(m_agentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetCounters()
|
|
||||||
{
|
|
||||||
ResendThrottle.Reset();
|
|
||||||
LandThrottle.Reset();
|
|
||||||
WindThrottle.Reset();
|
|
||||||
CloudThrottle.Reset();
|
|
||||||
TaskThrottle.Reset();
|
|
||||||
AssetThrottle.Reset();
|
|
||||||
TextureThrottle.Reset();
|
|
||||||
TotalThrottle.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool PacketsWaiting()
|
|
||||||
{
|
|
||||||
return (ResendOutgoingPacketQueue.Count > 0 ||
|
|
||||||
LandOutgoingPacketQueue.Count > 0 ||
|
|
||||||
WindOutgoingPacketQueue.Count > 0 ||
|
|
||||||
CloudOutgoingPacketQueue.Count > 0 ||
|
|
||||||
TaskOutgoingPacketQueue.Count > 0 ||
|
|
||||||
TaskLowpriorityPacketQueue.Count > 0 ||
|
|
||||||
AssetOutgoingPacketQueue.Count > 0 ||
|
|
||||||
TextureOutgoingPacketQueue.Count > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ProcessThrottle()
|
|
||||||
{
|
|
||||||
// I was considering this.. Will an event fire if the thread it's on is blocked?
|
|
||||||
|
|
||||||
// Then I figured out.. it doesn't really matter.. because this thread won't be blocked for long
|
|
||||||
// The General overhead of the UDP protocol gets sent to the queue un-throttled by this
|
|
||||||
// so This'll pick up about around the right time.
|
|
||||||
|
|
||||||
int MaxThrottleLoops = 4550; // 50*7 packets can be dequeued at once.
|
|
||||||
int throttleLoops = 0;
|
|
||||||
List<ThrottleOutPacketType> e;
|
|
||||||
|
|
||||||
// We're going to dequeue all of the saved up packets until
|
|
||||||
// we've hit the throttle limit or there's no more packets to send
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
// this variable will be true if there was work done in the last execution of the
|
|
||||||
// loop, since each pass through the loop checks the queue length, we no longer
|
|
||||||
// need the check on entering the loop
|
|
||||||
bool qchanged = true;
|
|
||||||
|
|
||||||
ResetCounters();
|
|
||||||
|
|
||||||
while (TotalThrottle.UnderLimit() && qchanged && throttleLoops <= MaxThrottleLoops)
|
|
||||||
{
|
|
||||||
qchanged = false; // We will break out of the loop if no work was accomplished
|
|
||||||
|
|
||||||
throttleLoops++;
|
|
||||||
//Now comes the fun part.. we dump all our elements into m_packetQueue that we've saved up.
|
|
||||||
if ((ResendOutgoingPacketQueue.Count > 0) && ResendThrottle.UnderLimit())
|
|
||||||
{
|
|
||||||
LLQueItem qpack = ResendOutgoingPacketQueue.Dequeue();
|
|
||||||
|
|
||||||
SendQueue.Enqueue(qpack);
|
|
||||||
TotalThrottle.AddBytes(qpack.Length);
|
|
||||||
ResendThrottle.AddBytes(qpack.Length);
|
|
||||||
|
|
||||||
qchanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((LandOutgoingPacketQueue.Count > 0) && LandThrottle.UnderLimit())
|
|
||||||
{
|
|
||||||
LLQueItem qpack = LandOutgoingPacketQueue.Dequeue();
|
|
||||||
|
|
||||||
SendQueue.Enqueue(qpack);
|
|
||||||
TotalThrottle.AddBytes(qpack.Length);
|
|
||||||
LandThrottle.AddBytes(qpack.Length);
|
|
||||||
qchanged = true;
|
|
||||||
|
|
||||||
if (LandOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Land))
|
|
||||||
Empty.Add(ThrottleOutPacketType.Land);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((WindOutgoingPacketQueue.Count > 0) && WindThrottle.UnderLimit())
|
|
||||||
{
|
|
||||||
LLQueItem qpack = WindOutgoingPacketQueue.Dequeue();
|
|
||||||
|
|
||||||
SendQueue.Enqueue(qpack);
|
|
||||||
TotalThrottle.AddBytes(qpack.Length);
|
|
||||||
WindThrottle.AddBytes(qpack.Length);
|
|
||||||
qchanged = true;
|
|
||||||
|
|
||||||
if (WindOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Wind))
|
|
||||||
Empty.Add(ThrottleOutPacketType.Wind);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((CloudOutgoingPacketQueue.Count > 0) && CloudThrottle.UnderLimit())
|
|
||||||
{
|
|
||||||
LLQueItem qpack = CloudOutgoingPacketQueue.Dequeue();
|
|
||||||
|
|
||||||
SendQueue.Enqueue(qpack);
|
|
||||||
TotalThrottle.AddBytes(qpack.Length);
|
|
||||||
CloudThrottle.AddBytes(qpack.Length);
|
|
||||||
qchanged = true;
|
|
||||||
|
|
||||||
if (CloudOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Cloud))
|
|
||||||
Empty.Add(ThrottleOutPacketType.Cloud);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((TaskOutgoingPacketQueue.Count > 0 || TaskLowpriorityPacketQueue.Count > 0) && TaskThrottle.UnderLimit())
|
|
||||||
{
|
|
||||||
LLQueItem qpack;
|
|
||||||
if (TaskOutgoingPacketQueue.Count > 0)
|
|
||||||
{
|
|
||||||
qpack = TaskOutgoingPacketQueue.Dequeue();
|
|
||||||
SendQueue.PriorityEnqueue(qpack);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qpack = TaskLowpriorityPacketQueue.Dequeue();
|
|
||||||
SendQueue.Enqueue(qpack);
|
|
||||||
}
|
|
||||||
|
|
||||||
TotalThrottle.AddBytes(qpack.Length);
|
|
||||||
TaskThrottle.AddBytes(qpack.Length);
|
|
||||||
qchanged = true;
|
|
||||||
|
|
||||||
if (TaskOutgoingPacketQueue.Count == 0 && TaskLowpriorityPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Task))
|
|
||||||
Empty.Add(ThrottleOutPacketType.Task);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((TextureOutgoingPacketQueue.Count > 0) && TextureThrottle.UnderLimit())
|
|
||||||
{
|
|
||||||
LLQueItem qpack = TextureOutgoingPacketQueue.Dequeue();
|
|
||||||
|
|
||||||
SendQueue.Enqueue(qpack);
|
|
||||||
TotalThrottle.AddBytes(qpack.Length);
|
|
||||||
TextureThrottle.AddBytes(qpack.Length);
|
|
||||||
qchanged = true;
|
|
||||||
|
|
||||||
if (TextureOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Texture))
|
|
||||||
Empty.Add(ThrottleOutPacketType.Texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((AssetOutgoingPacketQueue.Count > 0) && AssetThrottle.UnderLimit())
|
|
||||||
{
|
|
||||||
LLQueItem qpack = AssetOutgoingPacketQueue.Dequeue();
|
|
||||||
|
|
||||||
SendQueue.Enqueue(qpack);
|
|
||||||
TotalThrottle.AddBytes(qpack.Length);
|
|
||||||
AssetThrottle.AddBytes(qpack.Length);
|
|
||||||
qchanged = true;
|
|
||||||
|
|
||||||
if (AssetOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Asset))
|
|
||||||
Empty.Add(ThrottleOutPacketType.Asset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// m_log.Info("[THROTTLE]: Processed " + throttleLoops + " packets");
|
|
||||||
|
|
||||||
e = new List<ThrottleOutPacketType>(Empty);
|
|
||||||
Empty.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ThrottleOutPacketType t in e)
|
|
||||||
{
|
|
||||||
if (GetQueueCount(t) == 0)
|
|
||||||
TriggerOnQueueEmpty(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TriggerOnQueueEmpty(ThrottleOutPacketType queue)
|
|
||||||
{
|
|
||||||
QueueEmpty handlerQueueEmpty = OnQueueEmpty;
|
|
||||||
|
|
||||||
if (handlerQueueEmpty != null)
|
|
||||||
handlerQueueEmpty(queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThrottleTimerElapsed(object sender, ElapsedEventArgs e)
|
|
||||||
{
|
|
||||||
// just to change the signature, and that ProcessThrottle
|
|
||||||
// will be used elsewhere possibly
|
|
||||||
ProcessThrottle();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThrottleCheck(ref LLPacketThrottle throttle, ref Queue<LLQueItem> q, LLQueItem item, ThrottleOutPacketType itemType)
|
|
||||||
{
|
|
||||||
// The idea.. is if the packet throttle queues are empty
|
|
||||||
// and the client is under throttle for the type. Queue
|
|
||||||
// it up directly. This basically short cuts having to
|
|
||||||
// wait for the timer to fire to put things into the
|
|
||||||
// output queue
|
|
||||||
|
|
||||||
if ((q.Count == 0) && (throttle.UnderLimit()))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Monitor.Enter(this);
|
|
||||||
throttle.AddBytes(item.Length);
|
|
||||||
TotalThrottle.AddBytes(item.Length);
|
|
||||||
SendQueue.Enqueue(item);
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
if (!Empty.Contains(itemType))
|
|
||||||
Empty.Add(itemType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
// Probably a serialization exception
|
|
||||||
m_log.WarnFormat("ThrottleCheck: {0}", e.ToString());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Monitor.Pulse(this);
|
|
||||||
Monitor.Exit(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
q.Enqueue(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int ScaleThrottle(int value, int curmax, int newmax)
|
|
||||||
{
|
|
||||||
return (int)((value / (float)curmax) * newmax);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] GetThrottlesPacked(float multiplier)
|
|
||||||
{
|
|
||||||
int singlefloat = 4;
|
|
||||||
float tResend = ResendThrottle.Throttle*multiplier;
|
|
||||||
float tLand = LandThrottle.Throttle*multiplier;
|
|
||||||
float tWind = WindThrottle.Throttle*multiplier;
|
|
||||||
float tCloud = CloudThrottle.Throttle*multiplier;
|
|
||||||
float tTask = TaskThrottle.Throttle*multiplier;
|
|
||||||
float tTexture = TextureThrottle.Throttle*multiplier;
|
|
||||||
float tAsset = AssetThrottle.Throttle*multiplier;
|
|
||||||
|
|
||||||
byte[] throttles = new byte[singlefloat*7];
|
|
||||||
int i = 0;
|
|
||||||
Buffer.BlockCopy(BitConverter.GetBytes(tResend), 0, throttles, singlefloat*i, singlefloat);
|
|
||||||
i++;
|
|
||||||
Buffer.BlockCopy(BitConverter.GetBytes(tLand), 0, throttles, singlefloat*i, singlefloat);
|
|
||||||
i++;
|
|
||||||
Buffer.BlockCopy(BitConverter.GetBytes(tWind), 0, throttles, singlefloat*i, singlefloat);
|
|
||||||
i++;
|
|
||||||
Buffer.BlockCopy(BitConverter.GetBytes(tCloud), 0, throttles, singlefloat*i, singlefloat);
|
|
||||||
i++;
|
|
||||||
Buffer.BlockCopy(BitConverter.GetBytes(tTask), 0, throttles, singlefloat*i, singlefloat);
|
|
||||||
i++;
|
|
||||||
Buffer.BlockCopy(BitConverter.GetBytes(tTexture), 0, throttles, singlefloat*i, singlefloat);
|
|
||||||
i++;
|
|
||||||
Buffer.BlockCopy(BitConverter.GetBytes(tAsset), 0, throttles, singlefloat*i, singlefloat);
|
|
||||||
|
|
||||||
return throttles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetThrottleFromClient(byte[] throttle)
|
|
||||||
{
|
|
||||||
// From mantis http://opensimulator.org/mantis/view.php?id=1374
|
|
||||||
// it appears that sometimes we are receiving empty throttle byte arrays.
|
|
||||||
// TODO: Investigate this behaviour
|
|
||||||
if (throttle.Length == 0)
|
|
||||||
{
|
|
||||||
m_log.Warn("[PACKET QUEUE]: SetThrottleFromClient unexpectedly received a throttle byte array containing no elements!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int tResend = -1;
|
|
||||||
int tLand = -1;
|
|
||||||
int tWind = -1;
|
|
||||||
int tCloud = -1;
|
|
||||||
int tTask = -1;
|
|
||||||
int tTexture = -1;
|
|
||||||
int tAsset = -1;
|
|
||||||
int tall = -1;
|
|
||||||
int singlefloat = 4;
|
|
||||||
|
|
||||||
//Agent Throttle Block contains 7 single floatingpoint values.
|
|
||||||
int j = 0;
|
|
||||||
|
|
||||||
// Some Systems may be big endian...
|
|
||||||
// it might be smart to do this check more often...
|
|
||||||
if (!BitConverter.IsLittleEndian)
|
|
||||||
for (int i = 0; i < 7; i++)
|
|
||||||
Array.Reverse(throttle, j + i*singlefloat, singlefloat);
|
|
||||||
|
|
||||||
// values gotten from OpenMetaverse.org/wiki/Throttle. Thanks MW_
|
|
||||||
// bytes
|
|
||||||
// Convert to integer, since.. the full fp space isn't used.
|
|
||||||
tResend = (int) BitConverter.ToSingle(throttle, j);
|
|
||||||
j += singlefloat;
|
|
||||||
tLand = (int) BitConverter.ToSingle(throttle, j);
|
|
||||||
j += singlefloat;
|
|
||||||
tWind = (int) BitConverter.ToSingle(throttle, j);
|
|
||||||
j += singlefloat;
|
|
||||||
tCloud = (int) BitConverter.ToSingle(throttle, j);
|
|
||||||
j += singlefloat;
|
|
||||||
tTask = (int) BitConverter.ToSingle(throttle, j);
|
|
||||||
j += singlefloat;
|
|
||||||
tTexture = (int) BitConverter.ToSingle(throttle, j);
|
|
||||||
j += singlefloat;
|
|
||||||
tAsset = (int) BitConverter.ToSingle(throttle, j);
|
|
||||||
|
|
||||||
tall = tResend + tLand + tWind + tCloud + tTask + tTexture + tAsset;
|
|
||||||
|
|
||||||
/*
|
|
||||||
m_log.Info("[CLIENT]: Client AgentThrottle - Got throttle:resendbits=" + tResend +
|
|
||||||
" landbits=" + tLand +
|
|
||||||
" windbits=" + tWind +
|
|
||||||
" cloudbits=" + tCloud +
|
|
||||||
" taskbits=" + tTask +
|
|
||||||
" texturebits=" + tTexture +
|
|
||||||
" Assetbits=" + tAsset +
|
|
||||||
" Allbits=" + tall);
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
// Total Sanity
|
|
||||||
// Make sure that the client sent sane total values.
|
|
||||||
|
|
||||||
// If the client didn't send acceptable values....
|
|
||||||
// Scale the clients values down until they are acceptable.
|
|
||||||
|
|
||||||
if (tall <= TotalThrottle.Max)
|
|
||||||
{
|
|
||||||
ResendThrottle.Throttle = tResend;
|
|
||||||
LandThrottle.Throttle = tLand;
|
|
||||||
WindThrottle.Throttle = tWind;
|
|
||||||
CloudThrottle.Throttle = tCloud;
|
|
||||||
TaskThrottle.Throttle = tTask;
|
|
||||||
TextureThrottle.Throttle = tTexture;
|
|
||||||
AssetThrottle.Throttle = tAsset;
|
|
||||||
TotalThrottle.Throttle = tall;
|
|
||||||
}
|
|
||||||
// else if (tall < 1)
|
|
||||||
// {
|
|
||||||
// // client is stupid, penalize him by minning everything
|
|
||||||
// ResendThrottle.Throttle = ResendThrottle.Min;
|
|
||||||
// LandThrottle.Throttle = LandThrottle.Min;
|
|
||||||
// WindThrottle.Throttle = WindThrottle.Min;
|
|
||||||
// CloudThrottle.Throttle = CloudThrottle.Min;
|
|
||||||
// TaskThrottle.Throttle = TaskThrottle.Min;
|
|
||||||
// TextureThrottle.Throttle = TextureThrottle.Min;
|
|
||||||
// AssetThrottle.Throttle = AssetThrottle.Min;
|
|
||||||
// TotalThrottle.Throttle = TotalThrottle.Min;
|
|
||||||
// }
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// we're over so figure out percentages and use those
|
|
||||||
ResendThrottle.Throttle = tResend;
|
|
||||||
|
|
||||||
LandThrottle.Throttle = ScaleThrottle(tLand, tall, TotalThrottle.Max);
|
|
||||||
WindThrottle.Throttle = ScaleThrottle(tWind, tall, TotalThrottle.Max);
|
|
||||||
CloudThrottle.Throttle = ScaleThrottle(tCloud, tall, TotalThrottle.Max);
|
|
||||||
TaskThrottle.Throttle = ScaleThrottle(tTask, tall, TotalThrottle.Max);
|
|
||||||
TextureThrottle.Throttle = ScaleThrottle(tTexture, tall, TotalThrottle.Max);
|
|
||||||
AssetThrottle.Throttle = ScaleThrottle(tAsset, tall, TotalThrottle.Max);
|
|
||||||
TotalThrottle.Throttle = TotalThrottle.Max;
|
|
||||||
}
|
|
||||||
// effectively wiggling the slider causes things reset
|
|
||||||
// ResetCounters(); // DO NOT reset, better to send less for one period than more
|
|
||||||
}
|
|
||||||
|
|
||||||
// See IPullStatsProvider
|
|
||||||
public string GetStats()
|
|
||||||
{
|
|
||||||
return string.Format("{0,7} {1,7} {2,7} {3,7} {4,7} {5,7} {6,7} {7,7} {8,7} {9,7}",
|
|
||||||
SendQueue.Count(),
|
|
||||||
IncomingPacketQueue.Count,
|
|
||||||
OutgoingPacketQueue.Count,
|
|
||||||
ResendOutgoingPacketQueue.Count,
|
|
||||||
LandOutgoingPacketQueue.Count,
|
|
||||||
WindOutgoingPacketQueue.Count,
|
|
||||||
CloudOutgoingPacketQueue.Count,
|
|
||||||
TaskOutgoingPacketQueue.Count,
|
|
||||||
TextureOutgoingPacketQueue.Count,
|
|
||||||
AssetOutgoingPacketQueue.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LLQueItem[] GetQueueArray()
|
|
||||||
{
|
|
||||||
return SendQueue.GetQueueArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public float ThrottleMultiplier
|
|
||||||
{
|
|
||||||
get { return throttleMultiplier; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetQueueCount(ThrottleOutPacketType queue)
|
|
||||||
{
|
|
||||||
switch (queue)
|
|
||||||
{
|
|
||||||
case ThrottleOutPacketType.Land:
|
|
||||||
return LandOutgoingPacketQueue.Count;
|
|
||||||
case ThrottleOutPacketType.Wind:
|
|
||||||
return WindOutgoingPacketQueue.Count;
|
|
||||||
case ThrottleOutPacketType.Cloud:
|
|
||||||
return CloudOutgoingPacketQueue.Count;
|
|
||||||
case ThrottleOutPacketType.Task:
|
|
||||||
return TaskOutgoingPacketQueue.Count;
|
|
||||||
case ThrottleOutPacketType.Texture:
|
|
||||||
return TextureOutgoingPacketQueue.Count;
|
|
||||||
case ThrottleOutPacketType.Asset:
|
|
||||||
return AssetOutgoingPacketQueue.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,206 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) Contributors, http://opensimulator.org/
|
|
||||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
* * Neither the name of the OpenSimulator Project nor the
|
|
||||||
* names of its contributors may be used to endorse or promote products
|
|
||||||
* derived from this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
|
||||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using OpenMetaverse;
|
|
||||||
using OpenMetaverse.Packets;
|
|
||||||
using OpenSim.Framework;
|
|
||||||
|
|
||||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This class sets up new client stacks. It also handles the immediate distribution of incoming packets to
|
|
||||||
/// client stacks
|
|
||||||
/// </summary>
|
|
||||||
public class LLPacketServer
|
|
||||||
{
|
|
||||||
// private static readonly log4net.ILog m_log
|
|
||||||
// = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
||||||
|
|
||||||
protected readonly ILLClientStackNetworkHandler m_networkHandler;
|
|
||||||
protected IScene m_scene;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tweakable user settings
|
|
||||||
/// </summary>
|
|
||||||
private ClientStackUserSettings m_userSettings;
|
|
||||||
|
|
||||||
public LLPacketServer(ILLClientStackNetworkHandler 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,128 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
|
||||||
{
|
|
||||||
public class LLPacketThrottle
|
|
||||||
{
|
|
||||||
private readonly int m_maxAllowableThrottle;
|
|
||||||
private readonly int m_minAllowableThrottle;
|
|
||||||
private int m_currentThrottle;
|
|
||||||
private const int m_throttleTimeDivisor = 7;
|
|
||||||
private int m_currentBitsSent;
|
|
||||||
private int m_throttleBits;
|
|
||||||
|
|
||||||
/// <value>
|
|
||||||
/// Value with which to multiply all the throttle fields
|
|
||||||
/// </value>
|
|
||||||
private float m_throttleMultiplier;
|
|
||||||
|
|
||||||
public int Max
|
|
||||||
{
|
|
||||||
get { return m_maxAllowableThrottle; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Min
|
|
||||||
{
|
|
||||||
get { return m_minAllowableThrottle; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Current
|
|
||||||
{
|
|
||||||
get { return m_currentThrottle; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="min"></param>
|
|
||||||
/// <param name="max"></param>
|
|
||||||
/// <param name="throttle"></param>
|
|
||||||
/// <param name="throttleMultiplier">
|
|
||||||
/// A parameter that's ends up multiplying all throttle settings. An alternative solution would have been
|
|
||||||
/// to multiply all the parameters by this before giving them to the constructor. But doing it this way
|
|
||||||
/// represents the fact that the multiplier is a hack that pumps data to clients much faster than the actual
|
|
||||||
/// settings that we are given.
|
|
||||||
/// </param>
|
|
||||||
public LLPacketThrottle(int min, int max, int throttle, float throttleMultiplier)
|
|
||||||
{
|
|
||||||
m_throttleMultiplier = throttleMultiplier;
|
|
||||||
m_maxAllowableThrottle = max;
|
|
||||||
m_minAllowableThrottle = min;
|
|
||||||
m_currentThrottle = throttle;
|
|
||||||
m_currentBitsSent = 0;
|
|
||||||
|
|
||||||
CalcBits();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculate the actual throttle required.
|
|
||||||
/// </summary>
|
|
||||||
private void CalcBits()
|
|
||||||
{
|
|
||||||
m_throttleBits = (int)((float)m_currentThrottle * m_throttleMultiplier / (float)m_throttleTimeDivisor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
m_currentBitsSent = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool UnderLimit()
|
|
||||||
{
|
|
||||||
return m_currentBitsSent < m_throttleBits;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int AddBytes(int bytes)
|
|
||||||
{
|
|
||||||
m_currentBitsSent += bytes * 8;
|
|
||||||
return m_currentBitsSent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Throttle
|
|
||||||
{
|
|
||||||
get { return m_currentThrottle; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value < m_minAllowableThrottle)
|
|
||||||
{
|
|
||||||
m_currentThrottle = m_minAllowableThrottle;
|
|
||||||
}
|
|
||||||
else if (value > m_maxAllowableThrottle)
|
|
||||||
{
|
|
||||||
m_currentThrottle = m_maxAllowableThrottle;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_currentThrottle = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
CalcBits();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,442 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
{
|
||||||
|
#region Delegates
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when updated networking stats are produced for this client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inPackets">Number of incoming packets received since this
|
||||||
|
/// event was last fired</param>
|
||||||
|
/// <param name="outPackets">Number of outgoing packets sent since this
|
||||||
|
/// event was last fired</param>
|
||||||
|
/// <param name="unAckedBytes">Current total number of bytes in packets we
|
||||||
|
/// are waiting on ACKs for</param>
|
||||||
|
public delegate void PacketStats(int inPackets, int outPackets, int unAckedBytes);
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when the queue for a packet category is empty. This event can be
|
||||||
|
/// hooked to put more data on the empty queue
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="category">Category of the packet queue that is empty</param>
|
||||||
|
public delegate void QueueEmpty(ThrottleOutPacketType category);
|
||||||
|
|
||||||
|
#endregion Delegates
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks state for a client UDP connection and provides client-specific methods
|
||||||
|
/// </summary>
|
||||||
|
public sealed 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;
|
||||||
|
|
||||||
|
/// <summary>Fired when updated networking stats are produced for this client</summary>
|
||||||
|
public event PacketStats OnPacketStats;
|
||||||
|
/// <summary>Fired when the queue for a packet category is empty. This event can be
|
||||||
|
/// hooked to put more data on the empty queue</summary>
|
||||||
|
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>Number of packets received from this client</summary>
|
||||||
|
public int PacketsReceived;
|
||||||
|
/// <summary>Number of packets sent to this client</summary>
|
||||||
|
public int PacketsSent;
|
||||||
|
/// <summary>Total byte count of unacked packets sent to this client</summary>
|
||||||
|
public int UnackedBytes;
|
||||||
|
|
||||||
|
/// <summary>Total number of received packets that we have reported to the OnPacketStats event(s)</summary>
|
||||||
|
private int m_packetsReceivedReported;
|
||||||
|
/// <summary>Total number of sent packets that we have reported to the OnPacketStats event(s)</summary>
|
||||||
|
private int m_packetsSentReported;
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server">Reference to the UDP server this client is connected to</param>
|
||||||
|
/// <param name="rates">Default throttling rates and maximum throttle limits</param>
|
||||||
|
/// <param name="parentThrottle">Parent HTB (hierarchical token bucket)
|
||||||
|
/// that the child throttles will be governed by</param>
|
||||||
|
/// <param name="circuitCode">Circuit code for this connection</param>
|
||||||
|
/// <param name="agentID">AgentID for the connected agent</param>
|
||||||
|
/// <param name="remoteEndPoint">Remote endpoint for this connection</param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shuts down this client connection
|
||||||
|
/// </summary>
|
||||||
|
public void Shutdown()
|
||||||
|
{
|
||||||
|
// TODO: Do we need to invalidate the circuit?
|
||||||
|
IsConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets information about this client connection
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Information about the client connection</returns>
|
||||||
|
public ClientInfo GetClientInfo()
|
||||||
|
{
|
||||||
|
// TODO: This data structure is wrong in so many ways. Locking and copying the entire lists
|
||||||
|
// of pending and needed ACKs for every client every time some method wants information about
|
||||||
|
// this connection is a recipe for poor performance
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Modifies the UDP throttles
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">New throttling values</param>
|
||||||
|
public void SetClientInfo(ClientInfo info)
|
||||||
|
{
|
||||||
|
// TODO: Allowing throttles to be manually set from this function seems like a reasonable
|
||||||
|
// idea. On the other hand, letting external code manipulate our ACK accounting is not
|
||||||
|
// going to happen
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetStats()
|
||||||
|
{
|
||||||
|
// TODO: ???
|
||||||
|
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 SendPacketStats()
|
||||||
|
{
|
||||||
|
PacketStats callback = OnPacketStats;
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
int newPacketsReceived = PacketsReceived - m_packetsReceivedReported;
|
||||||
|
int newPacketsSent = PacketsSent - m_packetsSentReported;
|
||||||
|
|
||||||
|
callback(newPacketsReceived, newPacketsSent, UnackedBytes);
|
||||||
|
|
||||||
|
m_packetsReceivedReported += newPacketsReceived;
|
||||||
|
m_packetsSentReported += newPacketsSent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetThrottles(byte[] throttleData)
|
||||||
|
{
|
||||||
|
byte[] adjData;
|
||||||
|
int pos = 0;
|
||||||
|
|
||||||
|
if (!BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
byte[] newData = new byte[7 * 4];
|
||||||
|
Buffer.BlockCopy(throttleData, 0, newData, 0, 7 * 4);
|
||||||
|
|
||||||
|
for (int i = 0; i < 7; i++)
|
||||||
|
Array.Reverse(newData, i * 4, 4);
|
||||||
|
|
||||||
|
adjData = newData;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
adjData = throttleData;
|
||||||
|
}
|
||||||
|
|
||||||
|
int resend = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4;
|
||||||
|
int land = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4;
|
||||||
|
int wind = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4;
|
||||||
|
int cloud = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4;
|
||||||
|
int task = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4;
|
||||||
|
int texture = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4;
|
||||||
|
int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f);
|
||||||
|
|
||||||
|
resend = (resend <= defaultThrottleRates.ResendLimit) ? resend : defaultThrottleRates.ResendLimit;
|
||||||
|
land = (land <= defaultThrottleRates.LandLimit) ? land : defaultThrottleRates.LandLimit;
|
||||||
|
wind = (wind <= defaultThrottleRates.WindLimit) ? wind : defaultThrottleRates.WindLimit;
|
||||||
|
cloud = (cloud <= defaultThrottleRates.CloudLimit) ? cloud : defaultThrottleRates.CloudLimit;
|
||||||
|
task = (task <= defaultThrottleRates.TaskLimit) ? task : defaultThrottleRates.TaskLimit;
|
||||||
|
texture = (texture <= defaultThrottleRates.TextureLimit) ? texture : defaultThrottleRates.TextureLimit;
|
||||||
|
asset = (asset <= defaultThrottleRates.AssetLimit) ? asset : defaultThrottleRates.AssetLimit;
|
||||||
|
|
||||||
|
SetThrottle(ThrottleOutPacketType.Resend, resend);
|
||||||
|
SetThrottle(ThrottleOutPacketType.Land, land);
|
||||||
|
SetThrottle(ThrottleOutPacketType.Wind, wind);
|
||||||
|
SetThrottle(ThrottleOutPacketType.Cloud, cloud);
|
||||||
|
SetThrottle(ThrottleOutPacketType.Task, task);
|
||||||
|
SetThrottle(ThrottleOutPacketType.Texture, texture);
|
||||||
|
SetThrottle(ThrottleOutPacketType.Asset, asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetThrottlesPacked()
|
||||||
|
{
|
||||||
|
byte[] data = new byte[7 * 4];
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Resend].DripRate), 0, data, i, 4); i += 4;
|
||||||
|
Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Land].DripRate), 0, data, i, 4); i += 4;
|
||||||
|
Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate), 0, data, i, 4); i += 4;
|
||||||
|
Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate), 0, data, i, 4); i += 4;
|
||||||
|
Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Task].DripRate), 0, data, i, 4); i += 4;
|
||||||
|
Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate), 0, data, i, 4); i += 4;
|
||||||
|
Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate), 0, data, i, 4); i += 4;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetThrottle(ThrottleOutPacketType category, int rate)
|
||||||
|
{
|
||||||
|
int i = (int)category;
|
||||||
|
if (i >= 0 && i < throttleCategories.Length)
|
||||||
|
{
|
||||||
|
TokenBucket bucket = throttleCategories[(int)category];
|
||||||
|
bucket.MaxBurst = rate;
|
||||||
|
bucket.DripRate = rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnqueueOutgoing(OutgoingPacket packet)
|
||||||
|
{
|
||||||
|
int category = (int)packet.Category;
|
||||||
|
|
||||||
|
if (category >= 0 && category < packetOutboxes.Length)
|
||||||
|
{
|
||||||
|
LocklessQueue<OutgoingPacket> queue = packetOutboxes[category];
|
||||||
|
TokenBucket bucket = throttleCategories[category];
|
||||||
|
|
||||||
|
if (throttleCategories[category].RemoveTokens(packet.Buffer.DataLength))
|
||||||
|
{
|
||||||
|
// Enough tokens were removed from the bucket, the packet will not be queued
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not enough tokens in the bucket, queue this packet
|
||||||
|
queue.Enqueue(packet);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We don't have a token bucket for this category, so it will not be queued
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loops through all of the packet queues for this client and tries to send
|
||||||
|
/// any outgoing packets, obeying the throttling bucket limits
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This function is only called from a synchronous loop in the
|
||||||
|
/// UDPServer so we don't need to bother making this thread safe</remarks>
|
||||||
|
/// <returns>True if any packets were sent, otherwise false</returns>
|
||||||
|
public bool DequeueOutgoing()
|
||||||
|
{
|
||||||
|
OutgoingPacket packet;
|
||||||
|
LocklessQueue<OutgoingPacket> queue;
|
||||||
|
TokenBucket bucket;
|
||||||
|
bool packetSent = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++)
|
||||||
|
{
|
||||||
|
bucket = throttleCategories[i];
|
||||||
|
|
||||||
|
if (nextPackets[i] != null)
|
||||||
|
{
|
||||||
|
// This bucket was empty the last time we tried to send a packet,
|
||||||
|
// leaving a dequeued packet still waiting to be sent out. Try to
|
||||||
|
// send it again
|
||||||
|
if (bucket.RemoveTokens(nextPacketLengths[i]))
|
||||||
|
{
|
||||||
|
// Send the packet
|
||||||
|
udpServer.SendPacketFinal(nextPackets[i]);
|
||||||
|
nextPackets[i] = null;
|
||||||
|
packetSent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No dequeued packet waiting to be sent, try to pull one off
|
||||||
|
// this queue
|
||||||
|
queue = packetOutboxes[i];
|
||||||
|
if (queue.Dequeue(out packet))
|
||||||
|
{
|
||||||
|
// A packet was pulled off the queue. See if we have
|
||||||
|
// enough tokens in the bucket to send it out
|
||||||
|
if (bucket.RemoveTokens(packet.Buffer.DataLength))
|
||||||
|
{
|
||||||
|
// Send the packet
|
||||||
|
udpServer.SendPacketFinal(packet);
|
||||||
|
packetSent = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Save the dequeued packet and the length calculation for
|
||||||
|
// the next iteration
|
||||||
|
nextPackets[i] = packet;
|
||||||
|
nextPacketLengths[i] = packet.Buffer.DataLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No packets in this queue. Fire the queue empty callback
|
||||||
|
QueueEmpty callback = OnQueueEmpty;
|
||||||
|
if (callback != null)
|
||||||
|
callback((ThrottleOutPacketType)i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packetSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateRoundTrip(float r)
|
||||||
|
{
|
||||||
|
const float ALPHA = 0.125f;
|
||||||
|
const float BETA = 0.25f;
|
||||||
|
const float K = 4.0f;
|
||||||
|
|
||||||
|
if (RTTVAR == 0.0f)
|
||||||
|
{
|
||||||
|
// First RTT measurement
|
||||||
|
SRTT = r;
|
||||||
|
RTTVAR = r * 0.5f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Subsequence RTT measurement
|
||||||
|
RTTVAR = (1.0f - BETA) * RTTVAR + BETA * Math.Abs(SRTT - r);
|
||||||
|
SRTT = (1.0f - ALPHA) * SRTT + ALPHA * r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always round retransmission timeout up to two seconds
|
||||||
|
RTO = Math.Max(2000, (int)(SRTT + Math.Max(G, K * RTTVAR)));
|
||||||
|
//Logger.Debug("Setting agent " + this.Agent.FullName + "'s RTO to " + RTO + "ms with an RTTVAR of " +
|
||||||
|
// RTTVAR + " based on new RTT of " + r + "ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,282 @@
|
||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
object m_sync = new object();
|
||||||
|
|
||||||
|
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(); }
|
||||||
|
|
||||||
|
lock (m_sync)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
lock (m_sync)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
//rwLock.EnterWriteLock();
|
||||||
|
|
||||||
|
//try
|
||||||
|
//{
|
||||||
|
// Dictionary1.Clear();
|
||||||
|
// Dictionary2.Clear();
|
||||||
|
// Array = new LLUDPClient[0];
|
||||||
|
//}
|
||||||
|
//finally { rwLock.ExitWriteLock(); }
|
||||||
|
|
||||||
|
lock (m_sync)
|
||||||
|
{
|
||||||
|
Dictionary1.Clear();
|
||||||
|
Dictionary2.Clear();
|
||||||
|
Array = new LLUDPClient[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
//lock (m_sync)
|
||||||
|
// return Dictionary1.TryGetValue(key, out value);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Dictionary1.TryGetValue(key, out value);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
value = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
lock (m_sync)
|
||||||
|
return Dictionary2.TryGetValue(key, out value);
|
||||||
|
|
||||||
|
//try
|
||||||
|
//{
|
||||||
|
// return Dictionary2.TryGetValue(key, out value);
|
||||||
|
//}
|
||||||
|
//catch { }
|
||||||
|
//value = null;
|
||||||
|
//return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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(); }
|
||||||
|
|
||||||
|
LLUDPClient[] localArray = null;
|
||||||
|
lock (m_sync)
|
||||||
|
{
|
||||||
|
localArray = new LLUDPClient[Array.Length];
|
||||||
|
Array.CopyTo(localArray, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Parallel.ForEach<LLUDPClient>(localArray, action);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) Contributors, http://opensimulator.org/
|
* Copyright (c) Contributors, http://opensimulator.org/
|
||||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||||
*
|
*
|
||||||
|
@ -25,27 +25,46 @@
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using OpenSim.Framework;
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
|
|
||||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
{
|
{
|
||||||
public class LLUtil
|
/// <summary>
|
||||||
|
/// Holds a reference to the <seealso cref="LLUDPClient"/> this packet is
|
||||||
|
/// destined for, along with the serialized packet data, sequence number
|
||||||
|
/// (if this is a resend), number of times this packet has been resent,
|
||||||
|
/// the time of the last resend, and the throttling category for this
|
||||||
|
/// packet
|
||||||
|
/// </summary>
|
||||||
|
public sealed class OutgoingPacket
|
||||||
{
|
{
|
||||||
|
/// <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;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert a string to bytes suitable for use in an LL UDP packet.
|
/// Default constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="s">Truncated to 254 characters if necessary</param>
|
/// <param name="client">Reference to the client this packet is destined for</param>
|
||||||
/// <returns></returns>
|
/// <param name="buffer">Serialized packet data. If the flags or sequence number
|
||||||
public static byte[] StringToPacketBytes(string s)
|
/// need to be updated, they will be injected directly into this binary buffer</param>
|
||||||
|
/// <param name="category">Throttling category for this packet</param>
|
||||||
|
public OutgoingPacket(LLUDPClient client, UDPPacketBuffer buffer, ThrottleOutPacketType category)
|
||||||
{
|
{
|
||||||
// Anything more than 254 will cause libsecondlife to barf
|
Client = client;
|
||||||
// (libsl 1550) adds an \0 on the Utils.StringToBytes conversion if it isn't present
|
Buffer = buffer;
|
||||||
if (s.Length > 254)
|
Category = category;
|
||||||
{
|
|
||||||
s = s.Remove(254);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Utils.StringToBytes(s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -70,7 +70,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests
|
||||||
|
|
||||||
TestClient testClient = new TestClient(agent, scene);
|
TestClient testClient = new TestClient(agent, scene);
|
||||||
|
|
||||||
ILLPacketHandler packetHandler
|
LLPacketHandler packetHandler
|
||||||
= new LLPacketHandler(testClient, testLLPacketServer, new ClientStackUserSettings());
|
= new LLPacketHandler(testClient, testLLPacketServer, new ClientStackUserSettings());
|
||||||
|
|
||||||
packetHandler.InPacket(new AgentAnimationPacket());
|
packetHandler.InPacket(new AgentAnimationPacket());
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Dictionary<PacketType, int> m_packetsReceived = new Dictionary<PacketType, int>();
|
protected Dictionary<PacketType, int> m_packetsReceived = new Dictionary<PacketType, int>();
|
||||||
|
|
||||||
public TestLLPacketServer(ILLClientStackNetworkHandler networkHandler, ClientStackUserSettings userSettings)
|
public TestLLPacketServer(LLUDPServer networkHandler, ClientStackUserSettings userSettings)
|
||||||
: base(networkHandler, userSettings)
|
: base(networkHandler, userSettings)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Holds drip rates and maximum burst rates for throttling with hierarchical
|
||||||
|
/// token buckets. The maximum burst rates set here are hard limits and can
|
||||||
|
/// not be overridden by client requests
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ThrottleRates
|
||||||
|
{
|
||||||
|
/// <summary>Drip rate for resent packets</summary>
|
||||||
|
public int Resend;
|
||||||
|
/// <summary>Drip rate for terrain packets</summary>
|
||||||
|
public int Land;
|
||||||
|
/// <summary>Drip rate for wind packets</summary>
|
||||||
|
public int Wind;
|
||||||
|
/// <summary>Drip rate for cloud packets</summary>
|
||||||
|
public int Cloud;
|
||||||
|
/// <summary>Drip rate for task (state and transaction) packets</summary>
|
||||||
|
public int Task;
|
||||||
|
/// <summary>Drip rate for texture packets</summary>
|
||||||
|
public int Texture;
|
||||||
|
/// <summary>Drip rate for asset packets</summary>
|
||||||
|
public int Asset;
|
||||||
|
|
||||||
|
/// <summary>Maximum burst rate for resent packets</summary>
|
||||||
|
public int ResendLimit;
|
||||||
|
/// <summary>Maximum burst rate for land packets</summary>
|
||||||
|
public int LandLimit;
|
||||||
|
/// <summary>Maximum burst rate for wind packets</summary>
|
||||||
|
public int WindLimit;
|
||||||
|
/// <summary>Maximum burst rate for cloud packets</summary>
|
||||||
|
public int CloudLimit;
|
||||||
|
/// <summary>Maximum burst rate for task (state and transaction) packets</summary>
|
||||||
|
public int TaskLimit;
|
||||||
|
/// <summary>Maximum burst rate for texture packets</summary>
|
||||||
|
public int TextureLimit;
|
||||||
|
/// <summary>Maximum burst rate for asset packets</summary>
|
||||||
|
public int AssetLimit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config">Config source to load defaults from</param>
|
||||||
|
public ThrottleRates(IConfigSource config)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IConfig throttleConfig = config.Configs["ClientStack.LindenUDP"];
|
||||||
|
|
||||||
|
Resend = throttleConfig.GetInt("ResendDefault", 12500);
|
||||||
|
Land = throttleConfig.GetInt("LandDefault", 500);
|
||||||
|
Wind = throttleConfig.GetInt("WindDefault", 500);
|
||||||
|
Cloud = throttleConfig.GetInt("CloudDefault", 500);
|
||||||
|
Task = throttleConfig.GetInt("TaskDefault", 500);
|
||||||
|
Texture = throttleConfig.GetInt("TextureDefault", 500);
|
||||||
|
Asset = throttleConfig.GetInt("AssetDefault", 500);
|
||||||
|
|
||||||
|
ResendLimit = throttleConfig.GetInt("ResendLimit", 18750);
|
||||||
|
LandLimit = throttleConfig.GetInt("LandLimit", 29750);
|
||||||
|
WindLimit = throttleConfig.GetInt("WindLimit", 18750);
|
||||||
|
CloudLimit = throttleConfig.GetInt("CloudLimit", 18750);
|
||||||
|
TaskLimit = throttleConfig.GetInt("TaskLimit", 55750);
|
||||||
|
TextureLimit = throttleConfig.GetInt("TextureLimit", 55750);
|
||||||
|
AssetLimit = throttleConfig.GetInt("AssetLimit", 27500);
|
||||||
|
}
|
||||||
|
catch (Exception) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Special collection that is optimized for tracking unacknowledged packets
|
||||||
|
/// </summary>
|
||||||
|
public sealed class UnackedPacketCollection
|
||||||
|
{
|
||||||
|
/// <summary>Synchronization primitive. A lock must be acquired on this
|
||||||
|
/// object before calling any of the unsafe methods</summary>
|
||||||
|
public object SyncRoot = new object();
|
||||||
|
|
||||||
|
/// <summary>Holds the actual unacked packet data, sorted by sequence number</summary>
|
||||||
|
private SortedDictionary<uint, OutgoingPacket> packets = new SortedDictionary<uint, OutgoingPacket>();
|
||||||
|
|
||||||
|
/// <summary>Gets the total number of unacked packets</summary>
|
||||||
|
public int Count { get { return packets.Count; } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor
|
||||||
|
/// </summary>
|
||||||
|
public UnackedPacketCollection()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an unacked packet to the collection
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packet">Packet that is awaiting acknowledgement</param>
|
||||||
|
/// <returns>True if the packet was successfully added, false if the
|
||||||
|
/// packet already existed in the collection</returns>
|
||||||
|
public bool Add(OutgoingPacket packet)
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
if (!packets.ContainsKey(packet.SequenceNumber))
|
||||||
|
{
|
||||||
|
packets.Add(packet.SequenceNumber, packet);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a packet from the collection without attempting to obtain a
|
||||||
|
/// lock first
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sequenceNumber">Sequence number of the packet to remove</param>
|
||||||
|
/// <returns>True if the packet was found and removed, otherwise false</returns>
|
||||||
|
public bool RemoveUnsafe(uint sequenceNumber)
|
||||||
|
{
|
||||||
|
return packets.Remove(sequenceNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a packet from the collection without attempting to obtain a
|
||||||
|
/// lock first
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sequenceNumber">Sequence number of the packet to remove</param>
|
||||||
|
/// <param name="packet">Returns the removed packet</param>
|
||||||
|
/// <returns>True if the packet was found and removed, otherwise false</returns>
|
||||||
|
public bool RemoveUnsafe(uint sequenceNumber, out OutgoingPacket packet)
|
||||||
|
{
|
||||||
|
if (packets.TryGetValue(sequenceNumber, out packet))
|
||||||
|
{
|
||||||
|
packets.Remove(sequenceNumber);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the packet with the lowest sequence number
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The packet with the lowest sequence number, or null if the
|
||||||
|
/// collection is empty</returns>
|
||||||
|
public OutgoingPacket GetOldest()
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
using (SortedDictionary<uint, OutgoingPacket>.ValueCollection.Enumerator e = packets.Values.GetEnumerator())
|
||||||
|
{
|
||||||
|
if (e.MoveNext())
|
||||||
|
return e.Current;
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of all of the packets with a TickCount older than
|
||||||
|
/// the specified timeout
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeoutMS">Number of ticks (milliseconds) before a
|
||||||
|
/// packet is considered expired</param>
|
||||||
|
/// <returns>A list of all expired packets according to the given
|
||||||
|
/// expiration timeout</returns>
|
||||||
|
public List<OutgoingPacket> GetExpiredPackets(int timeoutMS)
|
||||||
|
{
|
||||||
|
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 >= timeoutMS)
|
||||||
|
{
|
||||||
|
if (expiredPackets == null)
|
||||||
|
expiredPackets = new List<OutgoingPacket>();
|
||||||
|
expiredPackets.Add(packet);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expiredPackets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -323,7 +323,6 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||||
httpThread.IsBackground = true;
|
httpThread.IsBackground = true;
|
||||||
_finished = false;
|
_finished = false;
|
||||||
httpThread.Start();
|
httpThread.Start();
|
||||||
ThreadTracker.Add(httpThread);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -639,7 +639,6 @@ namespace OpenSim.Region.CoreModules.Scripting.XMLRPC
|
||||||
httpThread.IsBackground = true;
|
httpThread.IsBackground = true;
|
||||||
_finished = false;
|
_finished = false;
|
||||||
httpThread.Start();
|
httpThread.Start();
|
||||||
ThreadTracker.Add(httpThread);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -345,7 +345,6 @@ namespace OpenSim.Region.CoreModules.World.WorldMap
|
||||||
mapItemReqThread.Priority = ThreadPriority.BelowNormal;
|
mapItemReqThread.Priority = ThreadPriority.BelowNormal;
|
||||||
mapItemReqThread.SetApartmentState(ApartmentState.MTA);
|
mapItemReqThread.SetApartmentState(ApartmentState.MTA);
|
||||||
mapItemReqThread.Start();
|
mapItemReqThread.Start();
|
||||||
ThreadTracker.Add(mapItemReqThread);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -447,7 +446,6 @@ namespace OpenSim.Region.CoreModules.World.WorldMap
|
||||||
// end gracefully
|
// end gracefully
|
||||||
if (st.agentID == UUID.Zero)
|
if (st.agentID == UUID.Zero)
|
||||||
{
|
{
|
||||||
ThreadTracker.Remove(mapItemReqThread);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -903,7 +903,6 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
//m_heartbeatTimer.Elapsed += new ElapsedEventHandler(Heartbeat);
|
//m_heartbeatTimer.Elapsed += new ElapsedEventHandler(Heartbeat);
|
||||||
if (HeartbeatThread != null)
|
if (HeartbeatThread != null)
|
||||||
{
|
{
|
||||||
ThreadTracker.Remove(HeartbeatThread);
|
|
||||||
HeartbeatThread.Abort();
|
HeartbeatThread.Abort();
|
||||||
HeartbeatThread = null;
|
HeartbeatThread = null;
|
||||||
}
|
}
|
||||||
|
@ -912,7 +911,6 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
HeartbeatThread.SetApartmentState(ApartmentState.MTA);
|
HeartbeatThread.SetApartmentState(ApartmentState.MTA);
|
||||||
HeartbeatThread.Name = string.Format("Heartbeat for region {0}", RegionInfo.RegionName);
|
HeartbeatThread.Name = string.Format("Heartbeat for region {0}", RegionInfo.RegionName);
|
||||||
HeartbeatThread.Priority = ThreadPriority.AboveNormal;
|
HeartbeatThread.Priority = ThreadPriority.AboveNormal;
|
||||||
ThreadTracker.Add(HeartbeatThread);
|
|
||||||
HeartbeatThread.Start();
|
HeartbeatThread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1448,6 +1446,9 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
m_log.Info("[SCENE]: Loading objects from datastore");
|
m_log.Info("[SCENE]: Loading objects from datastore");
|
||||||
|
|
||||||
List<SceneObjectGroup> PrimsFromDB = m_storageManager.DataStore.LoadObjects(regionID);
|
List<SceneObjectGroup> PrimsFromDB = m_storageManager.DataStore.LoadObjects(regionID);
|
||||||
|
|
||||||
|
m_log.Info("[SCENE]: Loaded " + PrimsFromDB.Count + " objects from the datastore");
|
||||||
|
|
||||||
foreach (SceneObjectGroup group in PrimsFromDB)
|
foreach (SceneObjectGroup group in PrimsFromDB)
|
||||||
{
|
{
|
||||||
if (group.RootPart == null)
|
if (group.RootPart == null)
|
||||||
|
|
|
@ -360,7 +360,6 @@ namespace OpenSim.Region.OptionalModules.Avatar.Chat
|
||||||
m_listener.Name = "IRCConnectorListenerThread";
|
m_listener.Name = "IRCConnectorListenerThread";
|
||||||
m_listener.IsBackground = true;
|
m_listener.IsBackground = true;
|
||||||
m_listener.Start();
|
m_listener.Start();
|
||||||
ThreadTracker.Add(m_listener);
|
|
||||||
|
|
||||||
// This is the message order recommended by RFC 2812
|
// This is the message order recommended by RFC 2812
|
||||||
if (m_password != null)
|
if (m_password != null)
|
||||||
|
|
|
@ -152,7 +152,6 @@ namespace OpenSim.Region.OptionalModules.ContentManagement
|
||||||
m_thread.Name = "Content Management";
|
m_thread.Name = "Content Management";
|
||||||
m_thread.IsBackground = true;
|
m_thread.IsBackground = true;
|
||||||
m_thread.Start();
|
m_thread.Start();
|
||||||
ThreadTracker.Add(m_thread);
|
|
||||||
m_state = State.NONE;
|
m_state = State.NONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,6 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
|
||||||
EventQueueThread.Priority = MyThreadPriority;
|
EventQueueThread.Priority = MyThreadPriority;
|
||||||
EventQueueThread.Name = "EventQueueManagerThread_" + ThreadCount;
|
EventQueueThread.Name = "EventQueueManagerThread_" + ThreadCount;
|
||||||
EventQueueThread.Start();
|
EventQueueThread.Start();
|
||||||
ThreadTracker.Add(EventQueueThread);
|
|
||||||
|
|
||||||
// Look at this... Don't you wish everyone did that solid
|
// Look at this... Don't you wish everyone did that solid
|
||||||
// coding everywhere? :P
|
// coding everywhere? :P
|
||||||
|
|
|
@ -97,7 +97,6 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
|
||||||
MaintenanceThreadThread.Name = "ScriptMaintenanceThread";
|
MaintenanceThreadThread.Name = "ScriptMaintenanceThread";
|
||||||
MaintenanceThreadThread.IsBackground = true;
|
MaintenanceThreadThread.IsBackground = true;
|
||||||
MaintenanceThreadThread.Start();
|
MaintenanceThreadThread.Start();
|
||||||
ThreadTracker.Add(MaintenanceThreadThread);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,10 +129,6 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
|
||||||
|
|
||||||
public void AddRegion(Scene Sceneworld)
|
public void AddRegion(Scene Sceneworld)
|
||||||
{
|
{
|
||||||
m_log.Info("[" + ScriptEngineName + "]: ScriptEngine initializing");
|
|
||||||
|
|
||||||
m_Scene = Sceneworld;
|
|
||||||
|
|
||||||
// Make sure we have config
|
// Make sure we have config
|
||||||
if (ConfigSource.Configs[ScriptEngineName] == null)
|
if (ConfigSource.Configs[ScriptEngineName] == null)
|
||||||
ConfigSource.AddConfig(ScriptEngineName);
|
ConfigSource.AddConfig(ScriptEngineName);
|
||||||
|
@ -143,6 +139,10 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
|
||||||
if (!m_enabled)
|
if (!m_enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
m_log.Info("[" + ScriptEngineName + "]: ScriptEngine initializing");
|
||||||
|
|
||||||
|
m_Scene = Sceneworld;
|
||||||
|
|
||||||
// Create all objects we'll be using
|
// Create all objects we'll be using
|
||||||
m_EventQueueManager = new EventQueueManager(this);
|
m_EventQueueManager = new EventQueueManager(this);
|
||||||
m_EventManager = new EventManager(this, true);
|
m_EventManager = new EventManager(this, true);
|
||||||
|
|
|
@ -142,7 +142,6 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||||
cmdHandlerThread.Priority = ThreadPriority.BelowNormal;
|
cmdHandlerThread.Priority = ThreadPriority.BelowNormal;
|
||||||
cmdHandlerThread.IsBackground = true;
|
cmdHandlerThread.IsBackground = true;
|
||||||
cmdHandlerThread.Start();
|
cmdHandlerThread.Start();
|
||||||
ThreadTracker.Add(cmdHandlerThread);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,6 @@ namespace OpenSim.TestSuite
|
||||||
m_td[pos].IsBackground = true;
|
m_td[pos].IsBackground = true;
|
||||||
m_td[pos].Start();
|
m_td[pos].Start();
|
||||||
m_lBot.Add(pb);
|
m_lBot.Add(pb);
|
||||||
ThreadTracker.Add(m_td[pos]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -55,7 +55,7 @@ namespace OpenSim.Tests.Clients.GridClient
|
||||||
new PatternLayout("%date [%thread] %-5level %logger [%property{NDC}] - %message%newline");
|
new PatternLayout("%date [%thread] %-5level %logger [%property{NDC}] - %message%newline");
|
||||||
log4net.Config.BasicConfigurator.Configure(consoleAppender);
|
log4net.Config.BasicConfigurator.Configure(consoleAppender);
|
||||||
|
|
||||||
string serverURI = "http://127.0.0.1:" + ConfigSettings.DefaultGridServerHttpPort;
|
string serverURI = "http://127.0.0.1:8001";
|
||||||
GridServicesConnector m_Connector = new GridServicesConnector(serverURI);
|
GridServicesConnector m_Connector = new GridServicesConnector(serverURI);
|
||||||
|
|
||||||
GridRegion r1 = CreateRegion("Test Region 1", 1000, 1000);
|
GridRegion r1 = CreateRegion("Test Region 1", 1000, 1000);
|
||||||
|
|
|
@ -149,7 +149,6 @@ namespace pCampBot
|
||||||
m_td[pos].IsBackground = true;
|
m_td[pos].IsBackground = true;
|
||||||
m_td[pos].Start();
|
m_td[pos].Start();
|
||||||
m_lBot.Add(pb);
|
m_lBot.Add(pb);
|
||||||
ThreadTracker.Add(m_td[pos]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -131,6 +131,7 @@
|
||||||
|
|
||||||
<ReferencePath>../../bin/</ReferencePath>
|
<ReferencePath>../../bin/</ReferencePath>
|
||||||
<Reference name="System"/>
|
<Reference name="System"/>
|
||||||
|
<Reference name="System.Core"/>
|
||||||
<Reference name="System.Xml"/>
|
<Reference name="System.Xml"/>
|
||||||
<Reference name="System.Data"/>
|
<Reference name="System.Data"/>
|
||||||
<Reference name="System.Drawing"/>
|
<Reference name="System.Drawing"/>
|
||||||
|
@ -1750,6 +1751,7 @@
|
||||||
|
|
||||||
<ReferencePath>../../../../bin/</ReferencePath>
|
<ReferencePath>../../../../bin/</ReferencePath>
|
||||||
<Reference name="System"/>
|
<Reference name="System"/>
|
||||||
|
<Reference name="System.Core"/>
|
||||||
<Reference name="System.Xml"/>
|
<Reference name="System.Xml"/>
|
||||||
<Reference name="OpenMetaverseTypes.dll"/>
|
<Reference name="OpenMetaverseTypes.dll"/>
|
||||||
<Reference name="OpenMetaverse.StructuredData.dll"/>
|
<Reference name="OpenMetaverse.StructuredData.dll"/>
|
||||||
|
@ -3689,6 +3691,7 @@
|
||||||
</Files>
|
</Files>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
<!-- Commented for now until new unit tests are written for the new LLUDP implementation
|
||||||
<Project frameworkVersion="v3_5" name="OpenSim.Region.ClientStack.LindenUDP.Tests" path="OpenSim/Region/ClientStack/LindenUDP/Tests" type="Library">
|
<Project frameworkVersion="v3_5" name="OpenSim.Region.ClientStack.LindenUDP.Tests" path="OpenSim/Region/ClientStack/LindenUDP/Tests" type="Library">
|
||||||
<Configuration name="Debug">
|
<Configuration name="Debug">
|
||||||
<Options>
|
<Options>
|
||||||
|
@ -3722,6 +3725,7 @@
|
||||||
<Match pattern="*.cs" recurse="false"/>
|
<Match pattern="*.cs" recurse="false"/>
|
||||||
</Files>
|
</Files>
|
||||||
</Project>
|
</Project>
|
||||||
|
-->
|
||||||
|
|
||||||
<Project frameworkVersion="v3_5" name="OpenSim.Region.ScriptEngine.Tests" path="OpenSim/Region/ScriptEngine" type="Library">
|
<Project frameworkVersion="v3_5" name="OpenSim.Region.ScriptEngine.Tests" path="OpenSim/Region/ScriptEngine" type="Library">
|
||||||
<Configuration name="Debug">
|
<Configuration name="Debug">
|
||||||
|
|
Loading…
Reference in New Issue