From 5d6e89eaf924e741be95248d3ce5bc4ddbd5de3c Mon Sep 17 00:00:00 2001
From: Tedd Hansen
Date: Fri, 1 Feb 2008 19:07:05 +0000
Subject: Highly experimental A separate thread is used to enforce max function
(event) execution time for scripts.
---
.../Common/ScriptEngineBase/EventQueueManager.cs | 248 ++++++++-------------
.../ScriptEngineBase/EventQueueThreadClass.cs | 201 +++++++++++++++++
2 files changed, 300 insertions(+), 149 deletions(-)
create mode 100644 OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueThreadClass.cs
(limited to 'OpenSim/Region/ScriptEngine/Common/ScriptEngineBase')
diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs
index 949d5b6..70db724 100644
--- a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs
+++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs
@@ -69,29 +69,31 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
///
/// List of threads processing event queue
///
- private List eventQueueThreads = new List();
+ private List eventQueueThreads = new List();
+ private object eventQueueThreadsLock = new object();
- private object queueLock = new object(); // Mutex lock object
+ public object queueLock = new object(); // Mutex lock object
///
- /// How many ms to sleep if queue is empty
+ /// How many threads to process queue with
///
- private int nothingToDoSleepms = 50;
+ private int numberOfThreads = 2;
///
- /// How many threads to process queue with
+ /// Maximum time one function can use for execution before we perform a thread kill
///
- private int numberOfThreads = 2;
+ private int maxFunctionExecutionTimems = 50;
+ private bool EnforceMaxExecutionTime = true;
///
/// Queue containing events waiting to be executed
///
- private Queue eventQueue = new Queue();
+ public Queue eventQueue = new Queue();
///
/// Queue item structure
///
- private struct QueueItemStruct
+ public struct QueueItemStruct
{
public uint localID;
public LLUUID itemID;
@@ -118,7 +120,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
public LSL_Types.Vector3[] _Vector3;
public bool[] _bool;
public int[] _int;
- public string [] _string;
+ public string[] _string;
}
///
@@ -128,177 +130,72 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
private object tryLockLock = new object(); // Mutex lock object
- private ScriptEngine m_ScriptEngine;
+ public ScriptEngine m_ScriptEngine;
+
+ public Thread ExecutionTimeoutEnforcingThread;
public EventQueueManager(ScriptEngine _ScriptEngine)
{
m_ScriptEngine = _ScriptEngine;
+ // 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)
//
- for (int ThreadCount = 0; ThreadCount <= numberOfThreads; ThreadCount++)
+ lock (eventQueueThreadsLock)
{
- Thread EventQueueThread = new Thread(EventQueueThreadLoop);
- eventQueueThreads.Add(EventQueueThread);
- EventQueueThread.IsBackground = true;
- EventQueueThread.Priority = ThreadPriority.BelowNormal;
- EventQueueThread.Name = "EventQueueManagerThread_" + ThreadCount;
- EventQueueThread.Start();
+ for (int ThreadCount = 0; ThreadCount <= numberOfThreads; ThreadCount++)
+ {
+ StartNewThreadClass();
+ }
}
}
~EventQueueManager()
{
- // Kill worker threads
- foreach (Thread EventQueueThread in new ArrayList(eventQueueThreads))
+ try
{
- if (EventQueueThread != null && EventQueueThread.IsAlive == true)
+ if (ExecutionTimeoutEnforcingThread != null)
{
- try
- {
- EventQueueThread.Abort();
- EventQueueThread.Join();
- }
- catch (Exception)
+ if (ExecutionTimeoutEnforcingThread.IsAlive)
{
- //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Exception killing worker thread: " + e.ToString());
+ ExecutionTimeoutEnforcingThread.Abort();
}
}
}
- eventQueueThreads.Clear();
- // Todo: Clean up our queues
- eventQueue.Clear();
- }
-
- ///
- /// Queue processing thread loop
- ///
- private void EventQueueThreadLoop()
- {
- //myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Worker thread spawned");
- try
+ catch (Exception ex)
{
- QueueItemStruct BlankQIS = new QueueItemStruct();
- while (true)
- {
- try
- {
- QueueItemStruct QIS = BlankQIS;
- bool GotItem = false;
+ }
- if (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 (queueLock)
- {
- GotItem = false;
- for (int qc = 0; qc < eventQueue.Count; qc++)
- {
- // Get queue item
- QIS = eventQueue.Dequeue();
-
- // Check if object is being processed by someone else
- if (TryLock(QIS.localID) == false)
- {
- // Object is already being processed, requeue it
- 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
- {
-#if DEBUG
- m_ScriptEngine.Log.Debug("ScriptEngine", "Executing event:\r\n"
- + "QIS.localID: " + QIS.localID
- + ", QIS.itemID: " + QIS.itemID
- + ", QIS.functionName: " + QIS.functionName);
-#endif
- m_ScriptEngine.m_ScriptManager.ExecuteEvent(QIS.localID, QIS.itemID,
- QIS.functionName, QIS.llDetectParams, QIS.param);
- }
- catch (Exception e)
- {
- // 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();
- }
- try
- {
- if (text.Length > 1500)
- text = text.Substring(0, 1500);
- IScriptHost m_host = m_ScriptEngine.World.GetSceneObjectPart(QIS.localID);
- //if (m_host != null)
- //{
- m_ScriptEngine.World.SimChat(Helpers.StringToField(text), ChatTypeEnum.Say, 0,
- m_host.AbsolutePosition, m_host.Name, m_host.UUID);
- }
- catch
- {
- //}
- //else
- //{
- // T oconsole
- m_ScriptEngine.Log.Error("ScriptEngine",
- "Unable to send text in-world:\r\n" + text);
- }
- }
- finally
- {
- ReleaseLock(QIS.localID);
- }
- }
- } // Something in queue
- }
- catch (ThreadAbortException tae)
- {
- throw tae;
- }
- catch (Exception e)
- {
- m_ScriptEngine.Log.Error("ScriptEngine", "Exception in EventQueueThreadLoop: " + e.ToString());
- }
- } // while
- } // try
- catch (ThreadAbortException)
+ // Kill worker threads
+ lock (eventQueueThreadsLock)
{
- //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Worker thread killed: " + tae.Message);
+ foreach (EventQueueThreadClass EventQueueThread in new ArrayList(eventQueueThreads))
+ {
+ EventQueueThread.Shutdown();
+ }
+ eventQueueThreads.Clear();
}
+ // Todo: Clean up our queues
+ eventQueue.Clear();
}
+
///
/// Try to get a mutex lock on localID
///
///
///
- private bool TryLock(uint localID)
+ public bool TryLock(uint localID)
{
lock (tryLockLock)
{
@@ -318,7 +215,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
/// Release mutex lock on localID
///
///
- private void ReleaseLock(uint localID)
+ public void ReleaseLock(uint localID)
{
lock (tryLockLock)
{
@@ -383,5 +280,58 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
eventQueue.Enqueue(QIS);
}
}
+
+ ///
+ /// A thread should run in this loop and check all running scripts
+ ///
+ public void ExecutionTimeoutEnforcingLoop()
+ {
+ try
+ {
+ while (true)
+ {
+ System.Threading.Thread.Sleep(maxFunctionExecutionTimems);
+ lock (eventQueueThreadsLock)
+ {
+ foreach (EventQueueThreadClass EventQueueThread in new ArrayList(eventQueueThreads))
+ {
+ if (EventQueueThread.InExecution)
+ {
+ if (DateTime.Now.Subtract(EventQueueThread.LastExecutionStarted).Milliseconds >
+ maxFunctionExecutionTimems)
+ {
+ // We need to kill this thread!
+ AbortThreadClass(EventQueueThread);
+ // Then start another
+ StartNewThreadClass();
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (ThreadAbortException tae)
+ {
+ }
+ }
+
+ private static 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());
+ }
+ }
+
+ private void StartNewThreadClass()
+ {
+ EventQueueThreadClass eqtc = new EventQueueThreadClass(this);
+ eventQueueThreads.Add(eqtc);
+ }
}
}
\ No newline at end of file
diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueThreadClass.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueThreadClass.cs
new file mode 100644
index 0000000..ad79fbc
--- /dev/null
+++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueThreadClass.cs
@@ -0,0 +1,201 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using libsecondlife;
+using OpenSim.Framework;
+using OpenSim.Region.Environment.Scenes.Scripting;
+
+namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
+{
+ ///
+ /// Because every thread needs some data set for it (time started to execute current function), it will do its work within a class
+ ///
+ public class EventQueueThreadClass
+ {
+ ///
+ /// How many ms to sleep if queue is empty
+ ///
+ private int nothingToDoSleepms = 50;
+
+ public DateTime LastExecutionStarted;
+ public bool InExecution = false;
+
+ private EventQueueManager eventQueueManager;
+ public Thread EventQueueThread;
+ private static int ThreadCount = 0;
+
+ public EventQueueThreadClass(EventQueueManager eqm)
+ {
+ eventQueueManager = eqm;
+ Start();
+ }
+
+ ~EventQueueThreadClass()
+ {
+ Shutdown();
+ }
+
+ ///
+ /// Start thread
+ ///
+ private void Start()
+ {
+ EventQueueThread = new Thread(EventQueueThreadLoop);
+ EventQueueThread.IsBackground = true;
+ EventQueueThread.Priority = ThreadPriority.BelowNormal;
+ EventQueueThread.Name = "EventQueueManagerThread_" + ThreadCount;
+ EventQueueThread.Start();
+
+ // Look at this... Don't you wish everyone did that solid coding everywhere? :P
+ if (ThreadCount == int.MaxValue)
+ ThreadCount = 0;
+ ThreadCount++;
+ }
+
+ public void Shutdown()
+ {
+ if (EventQueueThread != null && EventQueueThread.IsAlive == true)
+ {
+ try
+ {
+ EventQueueThread.Abort();
+ EventQueueThread.Join();
+ }
+ catch (Exception)
+ {
+ //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Exception killing worker thread: " + e.ToString());
+ }
+ }
+ }
+
+
+ ///
+ /// Queue processing thread loop
+ ///
+ private void EventQueueThreadLoop()
+ {
+ //myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Worker thread spawned");
+ try
+ {
+ EventQueueManager.QueueItemStruct BlankQIS = new EventQueueManager.QueueItemStruct();
+ while (true)
+ {
+ 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
+ {
+#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;
+ 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();
+ }
+ 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
+ {
+ InExecution = false;
+ eventQueueManager.ReleaseLock(QIS.localID);
+ }
+ }
+ } // Something in queue
+ }
+ catch (ThreadAbortException tae)
+ {
+ throw tae;
+ }
+ catch (Exception e)
+ {
+ eventQueueManager.m_ScriptEngine.Log.Error("ScriptEngine", "Exception in EventQueueThreadLoop: " + e.ToString());
+ }
+ } // while
+ } // try
+ catch (ThreadAbortException)
+ {
+ //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Worker thread killed: " + tae.Message);
+ }
+ }
+
+ }
+}
--
cgit v1.1