SCRIPT SUPPORT IS STILL BROKEN.
Bugfix: Scripts exceeding max and set to be killed were not killed, only removed. Added ability to re-read configuration while OpenSim is running All regions now sharing one MaintenanceThread New MaintenanceThread: - checks for script execution timeout - re-reads config - starts/stops threads if thread active count becomes too high/low compared to config Speed increase on event execution: - Reuse of try{}catch{} blocks - Time calculation on event executionThreadPoolClientBranch
parent
8a4e8a8e31
commit
a6726b0c9d
|
@ -65,12 +65,26 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
|
||||||
// increase number of threads to allow more concurrent script executions in OpenSim.
|
// increase number of threads to allow more concurrent script executions in OpenSim.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
public ScriptEngine m_ScriptEngine;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of threads processing event queue
|
/// List of threads (classes) processing event queue
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<EventQueueThreadClass> eventQueueThreads;// = new List<EventQueueThreadClass>();
|
internal List<EventQueueThreadClass> eventQueueThreads;
|
||||||
private object eventQueueThreadsLock;// = new object();
|
/// <summary>
|
||||||
|
/// Global static list of threads (classes) processing event queue -- used by max enforcment thread
|
||||||
|
/// </summary>
|
||||||
|
private List<EventQueueThreadClass> staticGlobalEventQueueThreads;
|
||||||
|
/// <summary>
|
||||||
|
/// Locking access to eventQueueThreads AND staticGlobalEventQueueThreads. Note that this may or may not be static depending on PrivateRegionThreads config setting.
|
||||||
|
/// </summary>
|
||||||
|
private object eventQueueThreadsLock;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used internally to specify how many threads should exit gracefully
|
||||||
|
/// </summary>
|
||||||
|
public int ThreadsToExit;
|
||||||
|
public object ThreadsToExitLock = new object();
|
||||||
|
|
||||||
private static List<EventQueueThreadClass> staticEventQueueThreads;// = new List<EventQueueThreadClass>();
|
private static List<EventQueueThreadClass> staticEventQueueThreads;// = new List<EventQueueThreadClass>();
|
||||||
private static object staticEventQueueThreadsLock;// = new object();
|
private static object staticEventQueueThreadsLock;// = new object();
|
||||||
|
@ -80,22 +94,43 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How many threads to process queue with
|
/// How many threads to process queue with
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int numberOfThreads;
|
internal int numberOfThreads;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum time one function can use for execution before we perform a thread kill
|
/// Maximum time one function can use for execution before we perform a thread kill.
|
||||||
|
/// </summary>
|
||||||
|
private int maxFunctionExecutionTimems
|
||||||
|
{
|
||||||
|
get { return (int)(maxFunctionExecutionTimens / 10000); }
|
||||||
|
set { maxFunctionExecutionTimens = value * 10000; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains nanoseconds version of maxFunctionExecutionTimems so that it matches time calculations better (performance reasons).
|
||||||
|
/// WARNING! ONLY UPDATE maxFunctionExecutionTimems, NEVER THIS DIRECTLY.
|
||||||
|
/// </summary>
|
||||||
|
public long maxFunctionExecutionTimens;
|
||||||
|
/// <summary>
|
||||||
|
/// Enforce max execution time
|
||||||
|
/// </summary>
|
||||||
|
public bool EnforceMaxExecutionTime;
|
||||||
|
/// <summary>
|
||||||
|
/// Kill script (unload) when it exceeds execution time
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int maxFunctionExecutionTimems;
|
|
||||||
private bool EnforceMaxExecutionTime;
|
|
||||||
private bool KillScriptOnMaxFunctionExecutionTime;
|
private bool KillScriptOnMaxFunctionExecutionTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of localID locks for mutex processing of script events
|
||||||
|
/// </summary>
|
||||||
|
private List<uint> objectLocks = new List<uint>();
|
||||||
|
private object tryLockLock = new object(); // Mutex lock object
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue containing events waiting to be executed
|
/// Queue containing events waiting to be executed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Queue<QueueItemStruct> eventQueue = new Queue<QueueItemStruct>();
|
public Queue<QueueItemStruct> eventQueue = new Queue<QueueItemStruct>();
|
||||||
|
|
||||||
|
#region " Queue structures "
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue item structure
|
/// Queue item structure
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -128,18 +163,9 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
|
||||||
public int[] _int;
|
public int[] _int;
|
||||||
public string[] _string;
|
public string[] _string;
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
#region " Initialization / Startup "
|
||||||
/// List of localID locks for mutex processing of script events
|
|
||||||
/// </summary>
|
|
||||||
private List<uint> objectLocks = new List<uint>();
|
|
||||||
|
|
||||||
private object tryLockLock = new object(); // Mutex lock object
|
|
||||||
|
|
||||||
public ScriptEngine m_ScriptEngine;
|
|
||||||
|
|
||||||
public Thread ExecutionTimeoutEnforcingThread;
|
|
||||||
|
|
||||||
public EventQueueManager(ScriptEngine _ScriptEngine)
|
public EventQueueManager(ScriptEngine _ScriptEngine)
|
||||||
{
|
{
|
||||||
m_ScriptEngine = _ScriptEngine;
|
m_ScriptEngine = _ScriptEngine;
|
||||||
|
@ -167,66 +193,79 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
|
||||||
eventQueueThreadsLock = staticEventQueueThreadsLock;
|
eventQueueThreadsLock = staticEventQueueThreadsLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
numberOfThreads = m_ScriptEngine.ScriptConfigSource.GetInt("NumberOfScriptThreads", 2);
|
ReadConfig();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadConfig()
|
||||||
|
{
|
||||||
|
numberOfThreads = m_ScriptEngine.ScriptConfigSource.GetInt("NumberOfScriptThreads", 2);
|
||||||
maxFunctionExecutionTimems = m_ScriptEngine.ScriptConfigSource.GetInt("MaxEventExecutionTimeMs", 5000);
|
maxFunctionExecutionTimems = m_ScriptEngine.ScriptConfigSource.GetInt("MaxEventExecutionTimeMs", 5000);
|
||||||
EnforceMaxExecutionTime = m_ScriptEngine.ScriptConfigSource.GetBoolean("EnforceMaxEventExecutionTime", false);
|
EnforceMaxExecutionTime = m_ScriptEngine.ScriptConfigSource.GetBoolean("EnforceMaxEventExecutionTime", false);
|
||||||
KillScriptOnMaxFunctionExecutionTime = m_ScriptEngine.ScriptConfigSource.GetBoolean("DeactivateScriptOnTimeout", false);
|
KillScriptOnMaxFunctionExecutionTime = m_ScriptEngine.ScriptConfigSource.GetBoolean("DeactivateScriptOnTimeout", false);
|
||||||
|
|
||||||
|
|
||||||
// Start function max exec time enforcement thread
|
|
||||||
if (EnforceMaxExecutionTime)
|
|
||||||
{
|
|
||||||
ExecutionTimeoutEnforcingThread = new Thread(ExecutionTimeoutEnforcingLoop);
|
|
||||||
ExecutionTimeoutEnforcingThread.Name = "ExecutionTimeoutEnforcingThread";
|
|
||||||
ExecutionTimeoutEnforcingThread.IsBackground = true;
|
|
||||||
ExecutionTimeoutEnforcingThread.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Start event queue processing threads (worker threads)
|
|
||||||
//
|
|
||||||
|
|
||||||
lock (eventQueueThreadsLock)
|
|
||||||
{
|
|
||||||
for (int ThreadCount = eventQueueThreads.Count; ThreadCount < numberOfThreads; ThreadCount++)
|
|
||||||
{
|
|
||||||
StartNewThreadClass();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region " Shutdown all threads "
|
||||||
~EventQueueManager()
|
~EventQueueManager()
|
||||||
{
|
{
|
||||||
try
|
Stop();
|
||||||
{
|
}
|
||||||
if (ExecutionTimeoutEnforcingThread != null)
|
|
||||||
{
|
|
||||||
if (ExecutionTimeoutEnforcingThread.IsAlive)
|
|
||||||
{
|
|
||||||
ExecutionTimeoutEnforcingThread.Abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private void Stop()
|
||||||
|
{
|
||||||
|
|
||||||
// Kill worker threads
|
// Kill worker threads
|
||||||
lock (eventQueueThreadsLock)
|
lock (eventQueueThreadsLock)
|
||||||
{
|
{
|
||||||
foreach (EventQueueThreadClass EventQueueThread in new ArrayList(eventQueueThreads))
|
foreach (EventQueueThreadClass EventQueueThread in eventQueueThreads)
|
||||||
{
|
{
|
||||||
EventQueueThread.Shutdown();
|
EventQueueThread.Shutdown();
|
||||||
}
|
}
|
||||||
eventQueueThreads.Clear();
|
eventQueueThreads.Clear();
|
||||||
|
staticGlobalEventQueueThreads.Clear();
|
||||||
|
}
|
||||||
|
// Remove all entries from our event queue
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
eventQueue.Clear();
|
||||||
}
|
}
|
||||||
// Todo: Clean up our queues
|
|
||||||
eventQueue.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region " Start / stop script execution threads (ThreadClasses) "
|
||||||
|
private void StartNewThreadClass()
|
||||||
|
{
|
||||||
|
EventQueueThreadClass eqtc = new EventQueueThreadClass(this);
|
||||||
|
eventQueueThreads.Add(eqtc);
|
||||||
|
staticGlobalEventQueueThreads.Add(eqtc);
|
||||||
|
m_ScriptEngine.Log.Debug("DotNetEngine", "Started new script execution thread. Current thread count: " + eventQueueThreads.Count);
|
||||||
|
|
||||||
|
}
|
||||||
|
private void AbortThreadClass(EventQueueThreadClass threadClass)
|
||||||
|
{
|
||||||
|
if (eventQueueThreads.Contains(threadClass))
|
||||||
|
eventQueueThreads.Remove(threadClass);
|
||||||
|
if (staticGlobalEventQueueThreads.Contains(threadClass))
|
||||||
|
staticGlobalEventQueueThreads.Remove(threadClass);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
threadClass.Shutdown();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
m_ScriptEngine.Log.Error("EventQueueManager", "If you see this, could you please report it to Tedd:");
|
||||||
|
m_ScriptEngine.Log.Error("EventQueueManager", "Script thread execution timeout kill ended in exception: " + ex.ToString());
|
||||||
|
}
|
||||||
|
m_ScriptEngine.Log.Debug("DotNetEngine", "Killed script execution thread. Remaining thread count: " + eventQueueThreads.Count);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region " Mutex locks for queue access "
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Try to get a mutex lock on localID
|
/// Try to get a mutex lock on localID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -262,8 +301,9 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region " Add events to execution queue "
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add event to event execution queue
|
/// Add event to event execution queue
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -317,62 +357,72 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
|
||||||
eventQueue.Enqueue(QIS);
|
eventQueue.Enqueue(QIS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region " Maintenance thread "
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A thread should run in this loop and check all running scripts
|
/// Adjust number of script thread classes. It can start new, but if it needs to stop it will just set number of threads in "ThreadsToExit" and threads will have to exit themselves.
|
||||||
|
/// Called from MaintenanceThread
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ExecutionTimeoutEnforcingLoop()
|
public void AdjustNumberOfScriptThreads()
|
||||||
{
|
{
|
||||||
try
|
lock (eventQueueThreadsLock)
|
||||||
{
|
{
|
||||||
while (true)
|
int diff = numberOfThreads - eventQueueThreads.Count;
|
||||||
|
// Positive number: Start
|
||||||
|
// Negative number: too many are running
|
||||||
|
if (diff > 0)
|
||||||
{
|
{
|
||||||
System.Threading.Thread.Sleep(maxFunctionExecutionTimems);
|
// We need to add more threads
|
||||||
lock (eventQueueThreadsLock)
|
for (int ThreadCount = eventQueueThreads.Count; ThreadCount < numberOfThreads; ThreadCount++)
|
||||||
{
|
{
|
||||||
foreach (EventQueueThreadClass EventQueueThread in new ArrayList(eventQueueThreads))
|
StartNewThreadClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (diff < 0)
|
||||||
|
{
|
||||||
|
// We need to kill some threads
|
||||||
|
lock (ThreadsToExitLock)
|
||||||
|
{
|
||||||
|
ThreadsToExit = Math.Abs(diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if any thread class has been executing an event too long
|
||||||
|
/// </summary>
|
||||||
|
public void CheckScriptMaxExecTime()
|
||||||
|
{
|
||||||
|
// Iterate through all ScriptThreadClasses and check how long their current function has been executing
|
||||||
|
lock (eventQueueThreadsLock)
|
||||||
|
{
|
||||||
|
foreach (EventQueueThreadClass EventQueueThread in staticGlobalEventQueueThreads)
|
||||||
|
{
|
||||||
|
// Is thread currently executing anything?
|
||||||
|
if (EventQueueThread.InExecution)
|
||||||
|
{
|
||||||
|
// Has execution time expired?
|
||||||
|
if (DateTime.Now.Ticks - EventQueueThread.LastExecutionStarted >
|
||||||
|
maxFunctionExecutionTimens)
|
||||||
{
|
{
|
||||||
if (EventQueueThread.InExecution)
|
// Yes! We need to kill this thread!
|
||||||
{
|
|
||||||
if (DateTime.Now.Subtract(EventQueueThread.LastExecutionStarted).Milliseconds >
|
// Set flag if script should be removed or not
|
||||||
maxFunctionExecutionTimems)
|
EventQueueThread.KillCurrentScript = KillScriptOnMaxFunctionExecutionTime;
|
||||||
{
|
|
||||||
// We need to kill this thread!
|
// Abort this thread
|
||||||
EventQueueThread.KillCurrentScript = KillScriptOnMaxFunctionExecutionTime;
|
AbortThreadClass(EventQueueThread);
|
||||||
AbortThreadClass(EventQueueThread);
|
|
||||||
// Then start another
|
// We do not need to start another, MaintenenceThread will do that for us
|
||||||
StartNewThreadClass();
|
//StartNewThreadClass();
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ThreadAbortException tae)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AbortThreadClass(EventQueueThreadClass threadClass)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
threadClass.Shutdown();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Could you please report this to Tedd:");
|
|
||||||
Console.WriteLine("Script thread execution timeout kill ended in exception: " + ex.ToString());
|
|
||||||
}
|
|
||||||
m_ScriptEngine.Log.Debug("DotNetEngine", "Killed script execution thread, count: " + eventQueueThreads.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartNewThreadClass()
|
|
||||||
{
|
|
||||||
EventQueueThreadClass eqtc = new EventQueueThreadClass(this);
|
|
||||||
eventQueueThreads.Add(eqtc);
|
|
||||||
m_ScriptEngine.Log.Debug("DotNetEngine", "Started new script execution thread, count: " + eventQueueThreads.Count);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,7 +19,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int nothingToDoSleepms;// = 50;
|
private int nothingToDoSleepms;// = 50;
|
||||||
|
|
||||||
public DateTime LastExecutionStarted;
|
public long LastExecutionStarted;
|
||||||
public bool InExecution = false;
|
public bool InExecution = false;
|
||||||
public bool KillCurrentScript = false;
|
public bool KillCurrentScript = false;
|
||||||
|
|
||||||
|
@ -109,120 +109,154 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
|
||||||
//myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Worker thread spawned");
|
//myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Worker thread spawned");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
EventQueueManager.QueueItemStruct BlankQIS = new EventQueueManager.QueueItemStruct();
|
while (true)
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
EventQueueManager.QueueItemStruct QIS = BlankQIS;
|
EventQueueManager.QueueItemStruct BlankQIS = new EventQueueManager.QueueItemStruct();
|
||||||
bool GotItem = false;
|
while (true)
|
||||||
|
|
||||||
if (eventQueueManager.eventQueue.Count == 0)
|
|
||||||
{
|
{
|
||||||
// Nothing to do? Sleep a bit waiting for something to do
|
// Every now and then check if we should shut down
|
||||||
Thread.Sleep(nothingToDoSleepms);
|
if (eventQueueManager.ThreadsToExit > 0)
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Something in queue, process
|
|
||||||
//myScriptEngine.m_logger.Verbose("ScriptEngine", "Processing event for localID: " + QIS.localID + ", itemID: " + QIS.itemID + ", FunctionName: " + QIS.FunctionName);
|
|
||||||
|
|
||||||
// OBJECT BASED LOCK - TWO THREADS WORKING ON SAME OBJECT IS NOT GOOD
|
|
||||||
lock (eventQueueManager.queueLock)
|
|
||||||
{
|
{
|
||||||
GotItem = false;
|
// Someone should shut down, lets get exclusive lock
|
||||||
for (int qc = 0; qc < eventQueueManager.eventQueue.Count; qc++)
|
lock (eventQueueManager.ThreadsToExitLock)
|
||||||
{
|
{
|
||||||
// Get queue item
|
// Lets re-check in case someone grabbed it
|
||||||
QIS = eventQueueManager.eventQueue.Dequeue();
|
if (eventQueueManager.ThreadsToExit > 0)
|
||||||
|
|
||||||
// Check if object is being processed by someone else
|
|
||||||
if (eventQueueManager.TryLock(QIS.localID) == false)
|
|
||||||
{
|
{
|
||||||
// Object is already being processed, requeue it
|
// We are go for shutdown
|
||||||
eventQueueManager.eventQueue.Enqueue(QIS);
|
eventQueueManager.ThreadsToExit--;
|
||||||
|
Shutdown();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// We have lock on an object and can process it
|
|
||||||
GotItem = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} // go through queue
|
|
||||||
} // lock
|
|
||||||
|
|
||||||
if (GotItem == true)
|
|
||||||
{
|
|
||||||
// Execute function
|
|
||||||
try
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
eventQueueManager.m_ScriptEngine.Log.Debug("ScriptEngine", "Executing event:\r\n"
|
|
||||||
+ "QIS.localID: " + QIS.localID
|
|
||||||
+ ", QIS.itemID: " + QIS.itemID
|
|
||||||
+ ", QIS.functionName: " + QIS.functionName);
|
|
||||||
#endif
|
|
||||||
LastExecutionStarted = DateTime.Now;
|
|
||||||
KillCurrentScript = false;
|
|
||||||
InExecution = true;
|
|
||||||
eventQueueManager.m_ScriptEngine.m_ScriptManager.ExecuteEvent(QIS.localID, QIS.itemID,
|
|
||||||
QIS.functionName, QIS.llDetectParams, QIS.param);
|
|
||||||
InExecution = false;
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
|
||||||
InExecution = false;
|
|
||||||
// DISPLAY ERROR INWORLD
|
|
||||||
string text = "Error executing script function \"" + QIS.functionName + "\":\r\n";
|
|
||||||
if (e.InnerException != null)
|
|
||||||
{
|
|
||||||
// Send inner exception
|
|
||||||
text += e.InnerException.Message.ToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
text += "\r\n";
|
|
||||||
// Send normal
|
|
||||||
text += e.Message.ToString();
|
|
||||||
}
|
|
||||||
if (KillCurrentScript)
|
|
||||||
text += "\r\nScript will be deactivated!";
|
|
||||||
|
|
||||||
|
|
||||||
|
//try
|
||||||
|
// {
|
||||||
|
EventQueueManager.QueueItemStruct QIS = BlankQIS;
|
||||||
|
bool GotItem = false;
|
||||||
|
|
||||||
|
if (eventQueueManager.eventQueue.Count == 0)
|
||||||
|
{
|
||||||
|
// Nothing to do? Sleep a bit waiting for something to do
|
||||||
|
Thread.Sleep(nothingToDoSleepms);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Something in queue, process
|
||||||
|
//myScriptEngine.m_logger.Verbose("ScriptEngine", "Processing event for localID: " + QIS.localID + ", itemID: " + QIS.itemID + ", FunctionName: " + QIS.FunctionName);
|
||||||
|
|
||||||
|
// OBJECT BASED LOCK - TWO THREADS WORKING ON SAME OBJECT IS NOT GOOD
|
||||||
|
lock (eventQueueManager.queueLock)
|
||||||
|
{
|
||||||
|
GotItem = false;
|
||||||
|
for (int qc = 0; qc < eventQueueManager.eventQueue.Count; qc++)
|
||||||
|
{
|
||||||
|
// Get queue item
|
||||||
|
QIS = eventQueueManager.eventQueue.Dequeue();
|
||||||
|
|
||||||
|
// Check if object is being processed by someone else
|
||||||
|
if (eventQueueManager.TryLock(QIS.localID) == false)
|
||||||
|
{
|
||||||
|
// Object is already being processed, requeue it
|
||||||
|
eventQueueManager.eventQueue.Enqueue(QIS);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We have lock on an object and can process it
|
||||||
|
GotItem = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} // go through queue
|
||||||
|
} // lock
|
||||||
|
|
||||||
|
if (GotItem == true)
|
||||||
|
{
|
||||||
|
// Execute function
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (text.Length > 1500)
|
#if DEBUG
|
||||||
text = text.Substring(0, 1500);
|
eventQueueManager.m_ScriptEngine.Log.Debug("ScriptEngine",
|
||||||
IScriptHost m_host = eventQueueManager.m_ScriptEngine.World.GetSceneObjectPart(QIS.localID);
|
"Executing event:\r\n"
|
||||||
//if (m_host != null)
|
+ "QIS.localID: " + QIS.localID
|
||||||
//{
|
+ ", QIS.itemID: " + QIS.itemID
|
||||||
eventQueueManager.m_ScriptEngine.World.SimChat(Helpers.StringToField(text), ChatTypeEnum.Say, 0,
|
+ ", QIS.functionName: " +
|
||||||
m_host.AbsolutePosition, m_host.Name, m_host.UUID);
|
QIS.functionName);
|
||||||
|
#endif
|
||||||
|
LastExecutionStarted = DateTime.Now.Ticks;
|
||||||
|
KillCurrentScript = false;
|
||||||
|
InExecution = true;
|
||||||
|
eventQueueManager.m_ScriptEngine.m_ScriptManager.ExecuteEvent(QIS.localID,
|
||||||
|
QIS.itemID,
|
||||||
|
QIS.functionName,
|
||||||
|
QIS.llDetectParams,
|
||||||
|
QIS.param);
|
||||||
|
InExecution = false;
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
//}
|
InExecution = false;
|
||||||
//else
|
// DISPLAY ERROR INWORLD
|
||||||
//{
|
string text = "Error executing script function \"" + QIS.functionName +
|
||||||
// T oconsole
|
"\":\r\n";
|
||||||
eventQueueManager.m_ScriptEngine.Log.Error("ScriptEngine",
|
if (e.InnerException != null)
|
||||||
"Unable to send text in-world:\r\n" + text);
|
{
|
||||||
|
// Send inner exception
|
||||||
|
text += e.InnerException.Message.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text += "\r\n";
|
||||||
|
// Send normal
|
||||||
|
text += e.Message.ToString();
|
||||||
|
}
|
||||||
|
if (KillCurrentScript)
|
||||||
|
text += "\r\nScript will be deactivated!";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (text.Length > 1500)
|
||||||
|
text = text.Substring(0, 1500);
|
||||||
|
IScriptHost m_host =
|
||||||
|
eventQueueManager.m_ScriptEngine.World.GetSceneObjectPart(QIS.localID);
|
||||||
|
//if (m_host != null)
|
||||||
|
//{
|
||||||
|
eventQueueManager.m_ScriptEngine.World.SimChat(Helpers.StringToField(text),
|
||||||
|
ChatTypeEnum.Say, 0,
|
||||||
|
m_host.AbsolutePosition,
|
||||||
|
m_host.Name, m_host.UUID);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
// T oconsole
|
||||||
|
eventQueueManager.m_ScriptEngine.Log.Error("ScriptEngine",
|
||||||
|
"Unable to send text in-world:\r\n" +
|
||||||
|
text);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// So we are done sending message in-world
|
||||||
|
if (KillCurrentScript)
|
||||||
|
{
|
||||||
|
eventQueueManager.m_ScriptEngine.m_ScriptManager.StopScript(
|
||||||
|
QIS.localID, QIS.itemID);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// So we are done sending message in-world
|
InExecution = false;
|
||||||
if (KillCurrentScript)
|
eventQueueManager.ReleaseLock(QIS.localID);
|
||||||
{
|
|
||||||
eventQueueManager.m_ScriptEngine.m_ScriptManager.RemoveScript(QIS.localID, QIS.itemID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
} // Something in queue
|
||||||
{
|
}
|
||||||
InExecution = false;
|
|
||||||
eventQueueManager.ReleaseLock(QIS.localID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // Something in queue
|
|
||||||
}
|
}
|
||||||
catch (ThreadAbortException tae)
|
catch (ThreadAbortException tae)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class does maintenance on script engine.
|
||||||
|
/// </summary>
|
||||||
|
public class MaintenanceThread
|
||||||
|
{
|
||||||
|
public ScriptEngine m_ScriptEngine;
|
||||||
|
private int MaintenanceLoopms;
|
||||||
|
|
||||||
|
public MaintenanceThread(ScriptEngine _ScriptEngine)
|
||||||
|
{
|
||||||
|
m_ScriptEngine = _ScriptEngine;
|
||||||
|
|
||||||
|
ReadConfig();
|
||||||
|
|
||||||
|
// Start maintenance thread
|
||||||
|
StartMaintenanceThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
~MaintenanceThread()
|
||||||
|
{
|
||||||
|
StopMaintenanceThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadConfig()
|
||||||
|
{
|
||||||
|
MaintenanceLoopms = m_ScriptEngine.ScriptConfigSource.GetInt("MaintenanceLoopms", 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region " Maintenance thread "
|
||||||
|
/// <summary>
|
||||||
|
/// Maintenance thread. Enforcing max execution time for example.
|
||||||
|
/// </summary>
|
||||||
|
public static Thread MaintenanceThreadThread;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts maintenance thread
|
||||||
|
/// </summary>
|
||||||
|
private void StartMaintenanceThread()
|
||||||
|
{
|
||||||
|
StopMaintenanceThread();
|
||||||
|
|
||||||
|
MaintenanceThreadThread = new Thread(MaintenanceLoop);
|
||||||
|
MaintenanceThreadThread.Name = "ScriptMaintenanceThread";
|
||||||
|
MaintenanceThreadThread.IsBackground = true;
|
||||||
|
MaintenanceThreadThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops maintenance thread
|
||||||
|
/// </summary>
|
||||||
|
private void StopMaintenanceThread()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (MaintenanceThreadThread != null)
|
||||||
|
{
|
||||||
|
if (MaintenanceThreadThread.IsAlive)
|
||||||
|
{
|
||||||
|
MaintenanceThreadThread.Abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
m_ScriptEngine.Log.Error("EventQueueManager", "Exception stopping maintenence thread: " + ex.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A thread should run in this loop and check all running scripts
|
||||||
|
/// </summary>
|
||||||
|
public void MaintenanceLoop()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
long Last_maxFunctionExecutionTimens = 0;// DateTime.Now.Ticks;
|
||||||
|
long Last_ReReadConfigFilens = DateTime.Now.Ticks;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
System.Threading.Thread.Sleep(MaintenanceLoopms); // Sleep
|
||||||
|
|
||||||
|
// Re-reading config every x seconds?
|
||||||
|
if (m_ScriptEngine.ReReadConfigFileSeconds > 0)
|
||||||
|
{
|
||||||
|
// Check if its time to re-read config
|
||||||
|
if (DateTime.Now.Ticks - Last_ReReadConfigFilens > m_ScriptEngine.ReReadConfigFilens)
|
||||||
|
{
|
||||||
|
// Its time to re-read config file
|
||||||
|
m_ScriptEngine.ConfigSource.Reload(); // Re-read config
|
||||||
|
Last_ReReadConfigFilens = DateTime.Now.Ticks; // Reset time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust number of running script threads if not correct
|
||||||
|
if (m_ScriptEngine.m_EventQueueManager.eventQueueThreads.Count != m_ScriptEngine.m_EventQueueManager.numberOfThreads)
|
||||||
|
{
|
||||||
|
m_ScriptEngine.m_EventQueueManager.AdjustNumberOfScriptThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check if any script has exceeded its max execution time
|
||||||
|
if (m_ScriptEngine.m_EventQueueManager.EnforceMaxExecutionTime)
|
||||||
|
{
|
||||||
|
if (DateTime.Now.Ticks - Last_maxFunctionExecutionTimens > m_ScriptEngine.m_EventQueueManager.maxFunctionExecutionTimens)
|
||||||
|
{
|
||||||
|
m_ScriptEngine.m_EventQueueManager.CheckScriptMaxExecTime(); // Do check
|
||||||
|
Last_maxFunctionExecutionTimens = DateTime.Now.Ticks; // Reset time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ThreadAbortException tae)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,6 +55,15 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
|
||||||
public IConfig ScriptConfigSource;
|
public IConfig ScriptConfigSource;
|
||||||
public abstract string ScriptConfigSourceName { get; }
|
public abstract string ScriptConfigSourceName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How many seconds between re-reading config-file. 0 = never. ScriptEngine will try to adjust to new config changes.
|
||||||
|
/// </summary>
|
||||||
|
public int ReReadConfigFileSeconds {
|
||||||
|
get { return (int)(ReReadConfigFilens / 10000); }
|
||||||
|
set { ReReadConfigFilens = value * 10000; }
|
||||||
|
}
|
||||||
|
public long ReReadConfigFilens = 0;
|
||||||
|
|
||||||
public ScriptManager GetScriptManager()
|
public ScriptManager GetScriptManager()
|
||||||
{
|
{
|
||||||
return _GetScriptManager();
|
return _GetScriptManager();
|
||||||
|
@ -93,6 +102,9 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
|
||||||
m_AppDomainManager = new AppDomainManager(ScriptConfigSource.GetInt("ScriptsPerAppDomain", 1));
|
m_AppDomainManager = new AppDomainManager(ScriptConfigSource.GetInt("ScriptsPerAppDomain", 1));
|
||||||
m_LSLLongCmdHandler = new LSLLongCmdHandler(this);
|
m_LSLLongCmdHandler = new LSLLongCmdHandler(this);
|
||||||
|
|
||||||
|
ReReadConfigFileSeconds = ScriptConfigSource.GetInt("ReReadConfig", 0);
|
||||||
|
|
||||||
|
|
||||||
// Should we iterate the region for scripts that needs starting?
|
// Should we iterate the region for scripts that needs starting?
|
||||||
// Or can we assume we are loaded before anything else so we can use proper events?
|
// Or can we assume we are loaded before anything else so we can use proper events?
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,7 @@ ScriptThreadPriority=BelowNormal
|
||||||
; true: Each region will get <NumberOfScriptThreads> dedicated to scripts within that region
|
; true: Each region will get <NumberOfScriptThreads> dedicated to scripts within that region
|
||||||
; Number of threads will be <NumberOfScriptThreads>*<NumberOfRegions>
|
; Number of threads will be <NumberOfScriptThreads>*<NumberOfRegions>
|
||||||
; false: All regions share <NumberOfScriptThreads> for all their scripts
|
; false: All regions share <NumberOfScriptThreads> for all their scripts
|
||||||
|
; Note! If you run multiple script engines based on "OpenSim.Region.ScriptEngine.Common" then all of them will share the same threads.
|
||||||
PrivateRegionThreads=false
|
PrivateRegionThreads=false
|
||||||
|
|
||||||
; How long MAX should a script event be allowed to run (per event execution)?
|
; How long MAX should a script event be allowed to run (per event execution)?
|
||||||
|
@ -163,3 +164,5 @@ SleepTimeIfNoScriptExecutionMs=50
|
||||||
; Each AppDomain has some memory overhead. But leaving dead scripts in memory also has memory overhead.
|
; Each AppDomain has some memory overhead. But leaving dead scripts in memory also has memory overhead.
|
||||||
ScriptsPerAppDomain=1
|
ScriptsPerAppDomain=1
|
||||||
|
|
||||||
|
; ReRead ScriptEngine config options how often?
|
||||||
|
ReReadConfig=0
|
||||||
|
|
Loading…
Reference in New Issue