using System; using System.Collections.Generic; using System.Text; using System.Threading; using libsecondlife; using Nini.Config; 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: iScriptEngineFunctionModule { /// /// How many ms to sleep if queue is empty /// private int nothingToDoSleepms;// = 50; private ThreadPriority MyThreadPriority; public long LastExecutionStarted; public bool InExecution = false; public bool KillCurrentScript = false; private EventQueueManager eventQueueManager; public Thread EventQueueThread; private static int ThreadCount = 0; private string ScriptEngineName = "ScriptEngine.Common"; public EventQueueThreadClass(EventQueueManager eqm) { eventQueueManager = eqm; ReadConfig(); Start(); } ~EventQueueThreadClass() { Stop(); } public void ReadConfig() { ScriptEngineName = eventQueueManager.m_ScriptEngine.ScriptEngineName; nothingToDoSleepms = eventQueueManager.m_ScriptEngine.ScriptConfigSource.GetInt("SleepTimeIfNoScriptExecutionMs", 50); // Later with ScriptServer we might want to ask OS for stuff too, so doing this a bit manually string pri = eventQueueManager.m_ScriptEngine.ScriptConfigSource.GetString("ScriptThreadPriority", "BelowNormal"); switch (pri.ToLower()) { case "lowest": MyThreadPriority = ThreadPriority.Lowest; break; case "belownormal": MyThreadPriority = ThreadPriority.BelowNormal; break; case "normal": MyThreadPriority = ThreadPriority.Normal; break; case "abovenormal": MyThreadPriority = ThreadPriority.AboveNormal; break; case "highest": MyThreadPriority = ThreadPriority.Highest; break; default: MyThreadPriority = ThreadPriority.BelowNormal; // Default eventQueueManager.m_ScriptEngine.Log.Error("ScriptEngineBase", "Unknown priority type \"" + pri + "\" in config file. Defaulting to \"BelowNormal\"."); break; } // Now set that priority if (EventQueueThread != null) if (EventQueueThread.IsAlive) EventQueueThread.Priority = MyThreadPriority; } /// /// Start thread /// private void Start() { EventQueueThread = new Thread(EventQueueThreadLoop); EventQueueThread.IsBackground = true; EventQueueThread.Priority = MyThreadPriority; 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 Stop() { PleaseShutdown = true; // Set shutdown flag Thread.Sleep(100); // Wait a bit if (EventQueueThread != null && EventQueueThread.IsAlive == true) { try { EventQueueThread.Abort(); // Send abort EventQueueThread.Join(); // Wait for it } catch (Exception) { //myScriptEngine.Log.Verbose(ScriptEngineName, "EventQueueManager Exception killing worker thread: " + e.ToString()); } } } /// /// Queue processing thread loop /// private void EventQueueThreadLoop() { //myScriptEngine.m_logger.Verbose(ScriptEngineName, "EventQueueManager Worker thread spawned"); try { while (true) { try { EventQueueManager.QueueItemStruct BlankQIS = new EventQueueManager.QueueItemStruct(); while (true) { // Every now and then check if we should shut down if (PleaseShutdown || eventQueueManager.ThreadsToExit > 0) { // Someone should shut down, lets get exclusive lock lock (eventQueueManager.ThreadsToExitLock) { // Lets re-check in case someone grabbed it if (eventQueueManager.ThreadsToExit > 0) { // Its crowded here so we'll shut down eventQueueManager.ThreadsToExit--; Stop(); return; } else { // We have been asked to shut down Stop(); return; } } } //try // { EventQueueManager.QueueItemStruct QIS = BlankQIS; bool GotItem = false; if (PleaseShutdown) return; 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(ScriptEngineName, "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(ScriptEngineName, "Executing event:\r\n" + "QIS.localID: " + QIS.localID + ", QIS.itemID: " + QIS.itemID + ", QIS.functionName: " + 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 (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 { 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(ScriptEngineName, "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 { InExecution = false; eventQueueManager.ReleaseLock(QIS.localID); } } } // Something in queue } } catch (ThreadAbortException tae) { eventQueueManager.m_ScriptEngine.Log.Notice(ScriptEngineName, "ThreadAbortException while executing function."); } catch (Exception e) { eventQueueManager.m_ScriptEngine.Log.Error(ScriptEngineName, "Exception in EventQueueThreadLoop: " + e.ToString()); } } // while } // try catch (ThreadAbortException) { //myScriptEngine.Log.Verbose(ScriptEngineName, "EventQueueManager Worker thread killed: " + tae.Message); } } /// /// If set to true then threads and stuff should try to make a graceful exit /// public bool PleaseShutdown { get { return _PleaseShutdown; } set { _PleaseShutdown = value; } } private bool _PleaseShutdown = false; } }