From d02a90823f2873f1b4de63062e3909d03a5a91fa Mon Sep 17 00:00:00 2001 From: Tedd Hansen Date: Fri, 1 Feb 2008 23:36:36 +0000 Subject: SCRIPTING STILL BROKEN Added comments and regions, restructured code Changed a lot of AppDomain junk from console from using Console.Write to Log.Verbose and set it to #if DEBUG All modules should now refresh their configuration runtime Made all logging in ScriptEngine.Common get script name from actual engine Renamed LSLLongCmdHandler to AsyncLSLCommandManager Added auto-recover with 5 sec throttle for new MaintenanceThread --- .../Region/ScriptEngine/Common/LSL_BaseClass.cs | 2 +- .../ScriptEngine/Common/LSL_BuiltIn_Commands.cs | 4 +- .../Common/ScriptEngineBase/AppDomainManager.cs | 44 ++- .../ScriptEngineBase/AsyncLSLCommandManager.cs | 313 +++++++++++++++++++++ .../ScriptEngine/Common/ScriptEngineBase/Common.cs | 4 +- .../Common/ScriptEngineBase/EventManager.cs | 21 +- .../Common/ScriptEngineBase/EventQueueManager.cs | 42 ++- .../ScriptEngineBase/EventQueueThreadClass.cs | 82 ++++-- .../Common/ScriptEngineBase/LSLLongCmdHandler.cs | 295 ------------------- .../Common/ScriptEngineBase/MaintenanceThread.cs | 86 ++++-- .../Common/ScriptEngineBase/ScriptEngine.cs | 70 +++-- .../Common/ScriptEngineBase/ScriptManager.cs | 23 +- .../iScriptEngineFunctionModule.cs | 12 + .../ScriptEngine/DotNetEngine/ScriptEngine.cs | 2 +- .../ScriptEngine/DotNetEngine/ScriptManager.cs | 2 +- .../Region/ScriptEngine/LSOEngine/ScriptEngine.cs | 2 +- .../Region/ScriptEngine/LSOEngine/ScriptManager.cs | 2 +- bin/OpenSim.ini.example | 21 +- 18 files changed, 622 insertions(+), 405 deletions(-) create mode 100644 OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/AsyncLSLCommandManager.cs delete mode 100644 OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/LSLLongCmdHandler.cs create mode 100644 OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/iScriptEngineFunctionModule.cs diff --git a/OpenSim/Region/ScriptEngine/Common/LSL_BaseClass.cs b/OpenSim/Region/ScriptEngine/Common/LSL_BaseClass.cs index dd46fa4..96a7dae 100644 --- a/OpenSim/Region/ScriptEngine/Common/LSL_BaseClass.cs +++ b/OpenSim/Region/ScriptEngine/Common/LSL_BaseClass.cs @@ -112,7 +112,7 @@ namespace OpenSim.Region.ScriptEngine.Common { m_LSL_Functions = LSL_Functions; - //MainLog.Instance.Notice("ScriptEngine", "LSL_BaseClass.Start() called."); + //MainLog.Instance.Notice(ScriptEngineName, "LSL_BaseClass.Start() called."); // Get this AppDomain's settings and display some of them. AppDomainSetup ads = AppDomain.CurrentDomain.SetupInformation; diff --git a/OpenSim/Region/ScriptEngine/Common/LSL_BuiltIn_Commands.cs b/OpenSim/Region/ScriptEngine/Common/LSL_BuiltIn_Commands.cs index df049d8..bbf301d 100644 --- a/OpenSim/Region/ScriptEngine/Common/LSL_BuiltIn_Commands.cs +++ b/OpenSim/Region/ScriptEngine/Common/LSL_BuiltIn_Commands.cs @@ -61,7 +61,7 @@ namespace OpenSim.Region.ScriptEngine.Common m_localID = localID; m_itemID = itemID; - //MainLog.Instance.Notice("ScriptEngine", "LSL_BaseClass.Start() called. Hosted by [" + m_host.Name + ":" + m_host.UUID + "@" + m_host.AbsolutePosition + "]"); + //MainLog.Instance.Notice(ScriptEngineName, "LSL_BaseClass.Start() called. Hosted by [" + m_host.Name + ":" + m_host.UUID + "@" + m_host.AbsolutePosition + "]"); } private DateTime m_timer = DateTime.Now; @@ -1038,7 +1038,7 @@ namespace OpenSim.Region.ScriptEngine.Common public void llSetTimerEvent(double sec) { // Setting timer repeat - m_ScriptEngine.m_LSLLongCmdHandler.SetTimerEvent(m_localID, m_itemID, sec); + m_ScriptEngine.m_ASYNCLSLCommandManager.SetTimerEvent(m_localID, m_itemID, sec); } public void llSleep(double sec) diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/AppDomainManager.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/AppDomainManager.cs index 3519d54..2ed0529 100644 --- a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/AppDomainManager.cs +++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/AppDomainManager.cs @@ -35,7 +35,7 @@ using OpenSim.Region.ScriptEngine.Common; namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase { - public class AppDomainManager + public class AppDomainManager : iScriptEngineFunctionModule { // @@ -85,12 +85,17 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase private object getLock = new object(); // Mutex private object freeLock = new object(); // Mutex - //private ScriptEngine m_scriptEngine; + private ScriptEngine m_scriptEngine; //public AppDomainManager(ScriptEngine scriptEngine) - public AppDomainManager(int MaxScriptsPerDomain) + public AppDomainManager(ScriptEngine scriptEngine) { - maxScriptsPerAppDomain = MaxScriptsPerDomain; - //m_scriptEngine = scriptEngine; + m_scriptEngine = scriptEngine; + ReadConfig(); + } + + public void ReadConfig() + { + maxScriptsPerAppDomain = m_scriptEngine.ScriptConfigSource.GetInt("ScriptsPerAppDomain", 1); } /// @@ -99,7 +104,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase /// Free AppDomain private AppDomainStructure GetFreeAppDomain() { - Console.WriteLine("Finding free AppDomain"); + // Console.WriteLine("Finding free AppDomain"); lock (getLock) { // Current full? @@ -117,7 +122,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase currentAD.CurrentAppDomain = PrepareNewAppDomain(); } - Console.WriteLine("Scripts loaded in this Appdomain: " + currentAD.ScriptsLoaded); + // Console.WriteLine("Scripts loaded in this Appdomain: " + currentAD.ScriptsLoaded); return currentAD; } // lock } @@ -144,7 +149,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase ads.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; AppDomain AD = AppDomain.CreateDomain("ScriptAppDomain_" + AppDomainNameCount, null, ads); - Console.WriteLine("Loading: " + + m_scriptEngine.Log.Verbose(m_scriptEngine.ScriptEngineName, "AppDomain Loading: " + AssemblyName.GetAssemblyName("OpenSim.Region.ScriptEngine.Common.dll").ToString()); AD.Load(AssemblyName.GetAssemblyName("OpenSim.Region.ScriptEngine.Common.dll")); @@ -169,17 +174,16 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase // Is number of unloaded bigger or equal to number of loaded? if (ads.ScriptsLoaded <= ads.ScriptsWaitingUnload) { - Console.WriteLine("Found empty AppDomain, unloading"); // Remove from internal list appDomains.Remove(ads); #if DEBUG + Console.WriteLine("Found empty AppDomain, unloading"); long m = GC.GetTotalMemory(true); #endif // Unload AppDomain.Unload(ads.CurrentAppDomain); #if DEBUG - Console.WriteLine("AppDomain unload freed " + (m - GC.GetTotalMemory(true)) + - " bytes of memory"); + m_scriptEngine.Log.Verbose(m_scriptEngine.ScriptEngineName, "AppDomain unload freed " + (m - GC.GetTotalMemory(true)) + " bytes of memory"); #endif } } @@ -193,7 +197,9 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase // Find next available AppDomain to put it in AppDomainStructure FreeAppDomain = GetFreeAppDomain(); - Console.WriteLine("Loading into AppDomain: " + FileName); +#if DEBUG + m_scriptEngine.Log.Verbose(m_scriptEngine.ScriptEngineName, "Loading into AppDomain: " + FileName); +#endif IScript mbrt = (IScript) FreeAppDomain.CurrentAppDomain.CreateInstanceFromAndUnwrap(FileName, "SecondLife.Script"); @@ -213,7 +219,9 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase { lock (freeLock) { - Console.WriteLine("Stopping script in AppDomain"); +#if DEBUG + m_scriptEngine.Log.Verbose(m_scriptEngine.ScriptEngineName, "Stopping script in AppDomain"); +#endif // Check if it is current AppDomain if (currentAD.CurrentAppDomain == ad) { @@ -236,5 +244,15 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase UnloadAppDomains(); // Outsite lock, has its own GetLock } + /// + /// 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; + } } \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/AsyncLSLCommandManager.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/AsyncLSLCommandManager.cs new file mode 100644 index 0000000..5ec8f50 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/AsyncLSLCommandManager.cs @@ -0,0 +1,313 @@ +/* +* 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 OpenSim 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; +using libsecondlife; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Modules; + +namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase +{ + /// + /// Handles LSL commands that takes long time and returns an event, for example timers, HTTP requests, etc. + /// + public class AsyncLSLCommandManager : iScriptEngineFunctionModule + { + private Thread cmdHandlerThread; + private int cmdHandlerThreadCycleSleepms; + + private ScriptEngine m_ScriptEngine; + + public AsyncLSLCommandManager(ScriptEngine _ScriptEngine) + { + m_ScriptEngine = _ScriptEngine; + ReadConfig(); + + // Start the thread that will be doing the work + cmdHandlerThread = new Thread(CmdHandlerThreadLoop); + cmdHandlerThread.Name = "CmdHandlerThread"; + cmdHandlerThread.Priority = ThreadPriority.BelowNormal; + cmdHandlerThread.IsBackground = true; + cmdHandlerThread.Start(); + } + + public void ReadConfig() + { + cmdHandlerThreadCycleSleepms = m_ScriptEngine.ScriptConfigSource.GetInt("AsyncLLCommandLoopms", 50); + } + + + ~AsyncLSLCommandManager() + { + // Shut down thread + try + { + if (cmdHandlerThread != null) + { + if (cmdHandlerThread.IsAlive == true) + { + cmdHandlerThread.Abort(); + cmdHandlerThread.Join(); + } + } + } + catch + { + } + } + + private void CmdHandlerThreadLoop() + { + while (true) + { + // Check timers + CheckTimerEvents(); + Thread.Sleep(25); + // Check HttpRequests + CheckHttpRequests(); + Thread.Sleep(25); + // Check XMLRPCRequests + CheckXMLRPCRequests(); + Thread.Sleep(25); + // Check Listeners + CheckListeners(); + Thread.Sleep(25); + + // Sleep before next cycle + //Thread.Sleep(cmdHandlerThreadCycleSleepms); + } + } + + /// + /// Remove a specific script (and all its pending commands) + /// + /// + /// + public void RemoveScript(uint localID, LLUUID itemID) + { + // Remove a specific script + + // Remove from: Timers + UnSetTimerEvents(localID, itemID); + // Remove from: HttpRequest + IHttpRequests iHttpReq = + m_ScriptEngine.World.RequestModuleInterface(); + iHttpReq.StopHttpRequest(localID, itemID); + } + + #region TIMER + + // + // TIMER + // + private class TimerClass + { + public uint localID; + public LLUUID itemID; + public double interval; + public DateTime next; + } + + private List Timers = new List(); + private object TimerListLock = new object(); + + public void SetTimerEvent(uint m_localID, LLUUID m_itemID, double sec) + { + Console.WriteLine("SetTimerEvent"); + + // Always remove first, in case this is a re-set + UnSetTimerEvents(m_localID, m_itemID); + if (sec == 0) // Disabling timer + return; + + // Add to timer + TimerClass ts = new TimerClass(); + ts.localID = m_localID; + ts.itemID = m_itemID; + ts.interval = sec; + ts.next = DateTime.Now.ToUniversalTime().AddSeconds(ts.interval); + lock (TimerListLock) + { + Timers.Add(ts); + } + } + + public void UnSetTimerEvents(uint m_localID, LLUUID m_itemID) + { + // Remove from timer + lock (TimerListLock) + { + List NewTimers = new List(); + foreach (TimerClass ts in Timers) + { + if (ts.localID != m_localID && ts.itemID != m_itemID) + { + NewTimers.Add(ts); + } + } + Timers.Clear(); + Timers = NewTimers; + } + } + + public void CheckTimerEvents() + { + // Nothing to do here? + if (Timers.Count == 0) + return; + + lock (TimerListLock) + { + // Go through all timers + foreach (TimerClass ts in Timers) + { + // Time has passed? + if (ts.next.ToUniversalTime() < DateTime.Now.ToUniversalTime()) + { + // Add it to queue + m_ScriptEngine.m_EventQueueManager.AddToScriptQueue(ts.localID, ts.itemID, "timer", EventQueueManager.llDetectNull, + new object[] {}); + // set next interval + + + ts.next = DateTime.Now.ToUniversalTime().AddSeconds(ts.interval); + } + } + } // lock + } + + #endregion + + #region HTTP REQUEST + + public void CheckHttpRequests() + { + if (m_ScriptEngine.World == null) + return; + + IHttpRequests iHttpReq = + m_ScriptEngine.World.RequestModuleInterface(); + + HttpRequestClass httpInfo = null; + + if (iHttpReq != null) + httpInfo = iHttpReq.GetNextCompletedRequest(); + + while (httpInfo != null) + { + //Console.WriteLine("PICKED HTTP REQ:" + httpInfo.response_body + httpInfo.status); + + // Deliver data to prim's remote_data handler + // + // TODO: Returning null for metadata, since the lsl function + // only returns the byte for HTTP_BODY_TRUNCATED, which is not + // implemented here yet anyway. Should be fixed if/when maxsize + // is supported + + object[] resobj = new object[] + { + httpInfo.reqID.ToString(), httpInfo.status, null, httpInfo.response_body + }; + + m_ScriptEngine.m_EventQueueManager.AddToScriptQueue( + httpInfo.localID, httpInfo.itemID, "http_response", EventQueueManager.llDetectNull, resobj + ); + + httpInfo.Stop(); + httpInfo = null; + + httpInfo = iHttpReq.GetNextCompletedRequest(); + } + } + + #endregion + + public void CheckXMLRPCRequests() + { + if (m_ScriptEngine.World == null) + return; + + IXMLRPC xmlrpc = m_ScriptEngine.World.RequestModuleInterface(); + + if (xmlrpc != null) + { + while (xmlrpc.hasRequests()) + { + RPCRequestInfo rInfo = xmlrpc.GetNextRequest(); + //Console.WriteLine("PICKED REQUEST"); + + //Deliver data to prim's remote_data handler + object[] resobj = new object[] + { + 2, rInfo.GetChannelKey().ToString(), rInfo.GetMessageID().ToString(), String.Empty, + rInfo.GetIntValue(), + rInfo.GetStrVal() + }; + m_ScriptEngine.m_EventQueueManager.AddToScriptQueue( + rInfo.GetLocalID(), rInfo.GetItemID(), "remote_data", EventQueueManager.llDetectNull, resobj + ); + } + } + } + + public void CheckListeners() + { + if (m_ScriptEngine.World == null) + return; + IWorldComm comms = m_ScriptEngine.World.RequestModuleInterface(); + + while (comms.HasMessages()) + { + ListenerInfo lInfo = comms.GetNextMessage(); + + //Deliver data to prim's listen handler + object[] resobj = new object[] + { + lInfo.GetChannel(), lInfo.GetName(), lInfo.GetID().ToString(), lInfo.GetMessage() + }; + + m_ScriptEngine.m_EventQueueManager.AddToScriptQueue( + lInfo.GetLocalID(), lInfo.GetItemID(), "listen", EventQueueManager.llDetectNull, resobj + ); + } + } + + /// + /// 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; + + } +} \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/Common.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/Common.cs index bce26ff..fe6dfcd 100644 --- a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/Common.cs +++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/Common.cs @@ -44,14 +44,14 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase public static void SendToDebug(string Message) { //if (Debug == true) - mySE.Log.Verbose("ScriptEngine", "Debug: " + Message); + mySE.Log.Verbose(mySE.ScriptEngineName, "Debug: " + Message); //SendToDebugEvent("\r\n" + DateTime.Now.ToString("[HH:mm:ss] ") + Message); } public static void SendToLog(string Message) { //if (Debug == true) - mySE.Log.Verbose("ScriptEngine", "LOG: " + Message); + mySE.Log.Verbose(mySE.ScriptEngineName, "LOG: " + Message); //SendToLogEvent("\r\n" + DateTime.Now.ToString("[HH:mm:ss] ") + Message); } } diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventManager.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventManager.cs index 250a5df..678c3d0 100644 --- a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventManager.cs +++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventManager.cs @@ -37,7 +37,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase /// Prepares events so they can be directly executed upon a script by EventQueueManager, then queues it. /// [Serializable] - public class EventManager : OpenSim.Region.ScriptEngine.Common.ScriptServerInterfaces.RemoteEvents + public class EventManager : OpenSim.Region.ScriptEngine.Common.ScriptServerInterfaces.RemoteEvents, iScriptEngineFunctionModule { // @@ -59,12 +59,13 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase public EventManager(ScriptEngine _ScriptEngine, bool performHookUp) { myScriptEngine = _ScriptEngine; + ReadConfig(); // Hook up to events from OpenSim // We may not want to do it because someone is controlling us and will deliver events to us if (performHookUp) { - myScriptEngine.Log.Verbose("ScriptEngine", "Hooking up to server events"); + myScriptEngine.Log.Verbose(myScriptEngine.ScriptEngineName, "Hooking up to server events"); myScriptEngine.World.EventManager.OnObjectGrab += touch_start; myScriptEngine.World.EventManager.OnRezScript += OnRezScript; myScriptEngine.World.EventManager.OnRemoveScript += OnRemoveScript; @@ -73,6 +74,11 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase } } + public void ReadConfig() + { + } + + public void changed(uint localID, uint change) { // Add to queue for all scripts in localID, Object pass change. @@ -263,5 +269,16 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase { // myScriptEngine.m_EventQueueManager.AddToScriptQueue(localID, itemID, "http_response", EventQueueManager.llDetectNull); } + + /// + /// 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; + } } \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs index 04c084a..3ba4618 100644 --- a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs +++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs @@ -42,7 +42,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase /// Events are queued and executed in separate thread /// [Serializable] - public class EventQueueManager + public class EventQueueManager : iScriptEngineFunctionModule { // @@ -197,13 +197,22 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase } - private void ReadConfig() + public void ReadConfig() { + // Refresh config numberOfThreads = m_ScriptEngine.ScriptConfigSource.GetInt("NumberOfScriptThreads", 2); maxFunctionExecutionTimems = m_ScriptEngine.ScriptConfigSource.GetInt("MaxEventExecutionTimeMs", 5000); EnforceMaxExecutionTime = m_ScriptEngine.ScriptConfigSource.GetBoolean("EnforceMaxEventExecutionTime", false); KillScriptOnMaxFunctionExecutionTime = m_ScriptEngine.ScriptConfigSource.GetBoolean("DeactivateScriptOnTimeout", false); + // Now refresh config in all threads + lock (eventQueueThreadsLock) + { + foreach (EventQueueThreadClass EventQueueThread in eventQueueThreads) + { + EventQueueThread.ReadConfig(); + } + } } #endregion @@ -222,7 +231,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase { foreach (EventQueueThreadClass EventQueueThread in eventQueueThreads) { - EventQueueThread.Shutdown(); + AbortThreadClass(EventQueueThread); } eventQueueThreads.Clear(); staticGlobalEventQueueThreads.Clear(); @@ -243,7 +252,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase 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); + m_ScriptEngine.Log.Debug(m_ScriptEngine.ScriptEngineName, "Started new script execution thread. Current thread count: " + eventQueueThreads.Count); } private void AbortThreadClass(EventQueueThreadClass threadClass) @@ -252,16 +261,17 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase eventQueueThreads.Remove(threadClass); if (staticGlobalEventQueueThreads.Contains(threadClass)) staticGlobalEventQueueThreads.Remove(threadClass); + try { - threadClass.Shutdown(); + threadClass.Stop(); } 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.Error(m_ScriptEngine.ScriptEngineName + ":EventQueueManager", "If you see this, could you please report it to Tedd:"); + m_ScriptEngine.Log.Error(m_ScriptEngine.ScriptEngineName + ":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); + m_ScriptEngine.Log.Debug(m_ScriptEngine.ScriptEngineName, "Killed script execution thread. Remaining thread count: " + eventQueueThreads.Count); } #endregion @@ -313,7 +323,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase public void AddToObjectQueue(uint localID, string FunctionName, Queue_llDetectParams_Struct qParams, params object[] param) { // Determine all scripts in Object and add to their queue - //myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Adding localID: " + localID + ", FunctionName: " + FunctionName); + //myScriptEngine.m_logger.Verbose(ScriptEngineName, "EventQueueManager Adding localID: " + localID + ", FunctionName: " + FunctionName); // Do we have any scripts in this object at all? If not, return @@ -367,6 +377,10 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase /// public void AdjustNumberOfScriptThreads() { + // Is there anything here for us to do? + if (eventQueueThreads.Count == numberOfThreads) + return; + lock (eventQueueThreadsLock) { int diff = numberOfThreads - eventQueueThreads.Count; @@ -424,5 +438,15 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase } } #endregion + /// + /// 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; + } } \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueThreadClass.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueThreadClass.cs index e610c36..c19d641 100644 --- a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueThreadClass.cs +++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueThreadClass.cs @@ -12,12 +12,13 @@ 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 + 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; @@ -26,25 +27,27 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase private EventQueueManager eventQueueManager; public Thread EventQueueThread; private static int ThreadCount = 0; - private ThreadPriority MyThreadPriority; + + private string ScriptEngineName = "ScriptEngine.Common"; public EventQueueThreadClass(EventQueueManager eqm) { eventQueueManager = eqm; - nothingToDoSleepms = eqm.m_ScriptEngine.ScriptConfigSource.GetInt("SleepTimeIfNoScriptExecutionMs", 50); + ReadConfig(); Start(); } ~EventQueueThreadClass() { - Shutdown(); + Stop(); } - /// - /// Start thread - /// - private void Start() + + 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()) @@ -70,6 +73,19 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase 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; @@ -84,18 +100,20 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase ThreadCount++; } - public void Shutdown() + public void Stop() { + PleaseShutdown = true; // Set shutdown flag + Thread.Sleep(100); // Wait a bit if (EventQueueThread != null && EventQueueThread.IsAlive == true) { try { - EventQueueThread.Abort(); - EventQueueThread.Join(); + EventQueueThread.Abort(); // Send abort + EventQueueThread.Join(); // Wait for it } catch (Exception) { - //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Exception killing worker thread: " + e.ToString()); + //myScriptEngine.Log.Verbose(ScriptEngineName, "EventQueueManager Exception killing worker thread: " + e.ToString()); } } } @@ -106,10 +124,10 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase /// private void EventQueueThreadLoop() { - //myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Worker thread spawned"); + //myScriptEngine.m_logger.Verbose(ScriptEngineName, "EventQueueManager Worker thread spawned"); try { - while (true) + while (true) { try { @@ -117,7 +135,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase while (true) { // Every now and then check if we should shut down - if (eventQueueManager.ThreadsToExit > 0) + if (PleaseShutdown || eventQueueManager.ThreadsToExit > 0) { // Someone should shut down, lets get exclusive lock lock (eventQueueManager.ThreadsToExitLock) @@ -125,9 +143,15 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase // Lets re-check in case someone grabbed it if (eventQueueManager.ThreadsToExit > 0) { - // We are go for shutdown + // Its crowded here so we'll shut down eventQueueManager.ThreadsToExit--; - Shutdown(); + Stop(); + return; + } + else + { + // We have been asked to shut down + Stop(); return; } } @@ -139,6 +163,9 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase 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 @@ -147,7 +174,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase else { // Something in queue, process - //myScriptEngine.m_logger.Verbose("ScriptEngine", "Processing event for localID: " + QIS.localID + ", itemID: " + QIS.itemID + ", FunctionName: " + QIS.FunctionName); + //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) @@ -179,7 +206,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase try { #if DEBUG - eventQueueManager.m_ScriptEngine.Log.Debug("ScriptEngine", + eventQueueManager.m_ScriptEngine.Log.Debug(ScriptEngineName, "Executing event:\r\n" + "QIS.localID: " + QIS.localID + ", QIS.itemID: " + QIS.itemID @@ -235,7 +262,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase //else //{ // T oconsole - eventQueueManager.m_ScriptEngine.Log.Error("ScriptEngine", + eventQueueManager.m_ScriptEngine.Log.Error(ScriptEngineName, "Unable to send text in-world:\r\n" + text); } @@ -260,19 +287,28 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase } catch (ThreadAbortException tae) { - eventQueueManager.m_ScriptEngine.Log.Notice("ScriptEngine", "ThreadAbortException while executing function."); + eventQueueManager.m_ScriptEngine.Log.Notice(ScriptEngineName, "ThreadAbortException while executing function."); } catch (Exception e) { - eventQueueManager.m_ScriptEngine.Log.Error("ScriptEngine", "Exception in EventQueueThreadLoop: " + e.ToString()); + eventQueueManager.m_ScriptEngine.Log.Error(ScriptEngineName, "Exception in EventQueueThreadLoop: " + e.ToString()); } } // while } // try catch (ThreadAbortException) { - //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Worker thread killed: " + tae.Message); + //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; } } diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/LSLLongCmdHandler.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/LSLLongCmdHandler.cs deleted file mode 100644 index 7d66638..0000000 --- a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/LSLLongCmdHandler.cs +++ /dev/null @@ -1,295 +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 OpenSim 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; -using libsecondlife; -using OpenSim.Region.Environment.Interfaces; -using OpenSim.Region.Environment.Modules; - -namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase -{ - /// - /// Handles LSL commands that takes long time and returns an event, for example timers, HTTP requests, etc. - /// - public class LSLLongCmdHandler - { - private Thread cmdHandlerThread; - private int cmdHandlerThreadCycleSleepms = 100; - - private ScriptEngine m_ScriptEngine; - - public LSLLongCmdHandler(ScriptEngine _ScriptEngine) - { - m_ScriptEngine = _ScriptEngine; - - // Start the thread that will be doing the work - cmdHandlerThread = new Thread(CmdHandlerThreadLoop); - cmdHandlerThread.Name = "CmdHandlerThread"; - cmdHandlerThread.Priority = ThreadPriority.BelowNormal; - cmdHandlerThread.IsBackground = true; - cmdHandlerThread.Start(); - } - - ~LSLLongCmdHandler() - { - // Shut down thread - try - { - if (cmdHandlerThread != null) - { - if (cmdHandlerThread.IsAlive == true) - { - cmdHandlerThread.Abort(); - cmdHandlerThread.Join(); - } - } - } - catch - { - } - } - - private void CmdHandlerThreadLoop() - { - while (true) - { - // Check timers - CheckTimerEvents(); - Thread.Sleep(25); - // Check HttpRequests - CheckHttpRequests(); - Thread.Sleep(25); - // Check XMLRPCRequests - CheckXMLRPCRequests(); - Thread.Sleep(25); - // Check Listeners - CheckListeners(); - Thread.Sleep(25); - - // Sleep before next cycle - //Thread.Sleep(cmdHandlerThreadCycleSleepms); - } - } - - /// - /// Remove a specific script (and all its pending commands) - /// - /// - /// - public void RemoveScript(uint localID, LLUUID itemID) - { - // Remove a specific script - - // Remove from: Timers - UnSetTimerEvents(localID, itemID); - // Remove from: HttpRequest - IHttpRequests iHttpReq = - m_ScriptEngine.World.RequestModuleInterface(); - iHttpReq.StopHttpRequest(localID, itemID); - } - - #region TIMER - - // - // TIMER - // - private class TimerClass - { - public uint localID; - public LLUUID itemID; - public double interval; - public DateTime next; - } - - private List Timers = new List(); - private object TimerListLock = new object(); - - public void SetTimerEvent(uint m_localID, LLUUID m_itemID, double sec) - { - Console.WriteLine("SetTimerEvent"); - - // Always remove first, in case this is a re-set - UnSetTimerEvents(m_localID, m_itemID); - if (sec == 0) // Disabling timer - return; - - // Add to timer - TimerClass ts = new TimerClass(); - ts.localID = m_localID; - ts.itemID = m_itemID; - ts.interval = sec; - ts.next = DateTime.Now.ToUniversalTime().AddSeconds(ts.interval); - lock (TimerListLock) - { - Timers.Add(ts); - } - } - - public void UnSetTimerEvents(uint m_localID, LLUUID m_itemID) - { - // Remove from timer - lock (TimerListLock) - { - List NewTimers = new List(); - foreach (TimerClass ts in Timers) - { - if (ts.localID != m_localID && ts.itemID != m_itemID) - { - NewTimers.Add(ts); - } - } - Timers.Clear(); - Timers = NewTimers; - } - } - - public void CheckTimerEvents() - { - // Nothing to do here? - if (Timers.Count == 0) - return; - - lock (TimerListLock) - { - // Go through all timers - foreach (TimerClass ts in Timers) - { - // Time has passed? - if (ts.next.ToUniversalTime() < DateTime.Now.ToUniversalTime()) - { - // Add it to queue - m_ScriptEngine.m_EventQueueManager.AddToScriptQueue(ts.localID, ts.itemID, "timer", EventQueueManager.llDetectNull, - new object[] {}); - // set next interval - - - ts.next = DateTime.Now.ToUniversalTime().AddSeconds(ts.interval); - } - } - } // lock - } - - #endregion - - #region HTTP REQUEST - - public void CheckHttpRequests() - { - if (m_ScriptEngine.World == null) - return; - - IHttpRequests iHttpReq = - m_ScriptEngine.World.RequestModuleInterface(); - - HttpRequestClass httpInfo = null; - - if (iHttpReq != null) - httpInfo = iHttpReq.GetNextCompletedRequest(); - - while (httpInfo != null) - { - //Console.WriteLine("PICKED HTTP REQ:" + httpInfo.response_body + httpInfo.status); - - // Deliver data to prim's remote_data handler - // - // TODO: Returning null for metadata, since the lsl function - // only returns the byte for HTTP_BODY_TRUNCATED, which is not - // implemented here yet anyway. Should be fixed if/when maxsize - // is supported - - object[] resobj = new object[] - { - httpInfo.reqID.ToString(), httpInfo.status, null, httpInfo.response_body - }; - - m_ScriptEngine.m_EventQueueManager.AddToScriptQueue( - httpInfo.localID, httpInfo.itemID, "http_response", EventQueueManager.llDetectNull, resobj - ); - - httpInfo.Stop(); - httpInfo = null; - - httpInfo = iHttpReq.GetNextCompletedRequest(); - } - } - - #endregion - - public void CheckXMLRPCRequests() - { - if (m_ScriptEngine.World == null) - return; - - IXMLRPC xmlrpc = m_ScriptEngine.World.RequestModuleInterface(); - - if (xmlrpc != null) - { - while (xmlrpc.hasRequests()) - { - RPCRequestInfo rInfo = xmlrpc.GetNextRequest(); - //Console.WriteLine("PICKED REQUEST"); - - //Deliver data to prim's remote_data handler - object[] resobj = new object[] - { - 2, rInfo.GetChannelKey().ToString(), rInfo.GetMessageID().ToString(), String.Empty, - rInfo.GetIntValue(), - rInfo.GetStrVal() - }; - m_ScriptEngine.m_EventQueueManager.AddToScriptQueue( - rInfo.GetLocalID(), rInfo.GetItemID(), "remote_data", EventQueueManager.llDetectNull, resobj - ); - } - } - } - - public void CheckListeners() - { - if (m_ScriptEngine.World == null) - return; - IWorldComm comms = m_ScriptEngine.World.RequestModuleInterface(); - - while (comms.HasMessages()) - { - ListenerInfo lInfo = comms.GetNextMessage(); - - //Deliver data to prim's listen handler - object[] resobj = new object[] - { - lInfo.GetChannel(), lInfo.GetName(), lInfo.GetID().ToString(), lInfo.GetMessage() - }; - - m_ScriptEngine.m_EventQueueManager.AddToScriptQueue( - lInfo.GetLocalID(), lInfo.GetItemID(), "listen", EventQueueManager.llDetectNull, resobj - ); - } - } - } -} \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/MaintenanceThread.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/MaintenanceThread.cs index 9536291..105d47f 100644 --- a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/MaintenanceThread.cs +++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/MaintenanceThread.cs @@ -8,7 +8,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase /// /// This class does maintenance on script engine. /// - public class MaintenanceThread + public class MaintenanceThread : iScriptEngineFunctionModule { public ScriptEngine m_ScriptEngine; private int MaintenanceLoopms; @@ -28,7 +28,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase StopMaintenanceThread(); } - private void ReadConfig() + public void ReadConfig() { MaintenanceLoopms = m_ScriptEngine.ScriptConfigSource.GetInt("MaintenanceLoopms", 50); } @@ -80,48 +80,74 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase /// public void MaintenanceLoop() { - try + if (m_ScriptEngine.m_EventQueueManager.maxFunctionExecutionTimens < MaintenanceLoopms) + m_ScriptEngine.Log.Warn(m_ScriptEngine.ScriptEngineName, + "Configuration error: MaxEventExecutionTimeMs is less than MaintenanceLoopms. The Maintenance Loop will only check scripts once per run."); + + while (true) { - long Last_maxFunctionExecutionTimens = 0;// DateTime.Now.Ticks; - long Last_ReReadConfigFilens = DateTime.Now.Ticks; - while (true) + try { - System.Threading.Thread.Sleep(MaintenanceLoopms); // Sleep - - // Re-reading config every x seconds? - if (m_ScriptEngine.ReReadConfigFileSeconds > 0) + long Last_maxFunctionExecutionTimens = 0; // DateTime.Now.Ticks; + long Last_ReReadConfigFilens = DateTime.Now.Ticks; + while (true) { - // Check if its time to re-read config - if (DateTime.Now.Ticks - Last_ReReadConfigFilens > m_ScriptEngine.ReReadConfigFilens) + System.Threading.Thread.Sleep(MaintenanceLoopms); // Sleep before next pass + if (PleaseShutdown) + return; + // + // Re-reading config every x seconds + // + if (m_ScriptEngine.RefreshConfigFileSeconds > 0) { - // Its time to re-read config file - m_ScriptEngine.ConfigSource.Reload(); // Re-read config - Last_ReReadConfigFilens = DateTime.Now.Ticks; // Reset time + // Check if its time to re-read config + if (DateTime.Now.Ticks - Last_ReReadConfigFilens > m_ScriptEngine.RefreshConfigFilens) + { + // Its time to re-read config file + m_ScriptEngine.ConfigSource.Reload(); // Refresh config + m_ScriptEngine.ReadConfig(); + 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) - { + // + // Adjust number of running script threads if not correct + // 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) + // + // Check if any script has exceeded its max execution time + // + if (m_ScriptEngine.m_EventQueueManager.EnforceMaxExecutionTime) { - m_ScriptEngine.m_EventQueueManager.CheckScriptMaxExecTime(); // Do check - Last_maxFunctionExecutionTimens = DateTime.Now.Ticks; // Reset time + // We are enforcing execution time + if (DateTime.Now.Ticks - Last_maxFunctionExecutionTimens > + m_ScriptEngine.m_EventQueueManager.maxFunctionExecutionTimens) + { + // Its time to check again + m_ScriptEngine.m_EventQueueManager.CheckScriptMaxExecTime(); // Do check + Last_maxFunctionExecutionTimens = DateTime.Now.Ticks; // Reset time + } } } } - } - catch (ThreadAbortException tae) - { + catch (Exception ex) + { + m_ScriptEngine.Log.Error(m_ScriptEngine.ScriptEngineName, "Exception in MaintenanceLoopThread. Thread will recover after 5 sec throttle. Exception: " + ex.ToString()); + Thread.Sleep(5000); + } } } #endregion + /// + /// 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; + } } diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/ScriptEngine.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/ScriptEngine.cs index cfcc36e..c237282 100644 --- a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/ScriptEngine.cs +++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/ScriptEngine.cs @@ -42,27 +42,28 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase /// /// [Serializable] - public abstract class ScriptEngine : IRegionModule, OpenSim.Region.ScriptEngine.Common.ScriptServerInterfaces.ScriptEngine + public abstract class ScriptEngine : IRegionModule, OpenSim.Region.ScriptEngine.Common.ScriptServerInterfaces.ScriptEngine, iScriptEngineFunctionModule { public Scene World; - public EventManager m_EventManager; // Handles and queues incoming events from OpenSim - public EventQueueManager m_EventQueueManager; // Executes events - public ScriptManager m_ScriptManager; // Load, unload and execute scripts - public AppDomainManager m_AppDomainManager; - public LSLLongCmdHandler m_LSLLongCmdHandler; + public EventManager m_EventManager; // Handles and queues incoming events from OpenSim + public EventQueueManager m_EventQueueManager; // Executes events, handles script threads + public ScriptManager m_ScriptManager; // Load, unload and execute scripts + public AppDomainManager m_AppDomainManager; // Handles loading/unloading of scripts into AppDomains + public AsyncLSLCommandManager m_ASYNCLSLCommandManager; // Asyncronous LSL commands (commands that returns with an event) + public MaintenanceThread m_MaintenanceThread; // Thread that does different kinds of maintenance, for example refreshing config and killing scripts that has been running too long public IConfigSource ConfigSource; public IConfig ScriptConfigSource; - public abstract string ScriptConfigSourceName { get; } + public abstract string ScriptEngineName { get; } /// /// How many seconds between re-reading config-file. 0 = never. ScriptEngine will try to adjust to new config changes. /// - public int ReReadConfigFileSeconds { - get { return (int)(ReReadConfigFilens / 10000); } - set { ReReadConfigFilens = value * 10000; } + public int RefreshConfigFileSeconds { + get { return (int)(RefreshConfigFilens / 10000); } + set { RefreshConfigFilens = value * 10000; } } - public long ReReadConfigFilens = 0; + public long RefreshConfigFilens = 0; public ScriptManager GetScriptManager() { @@ -88,21 +89,22 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase { World = Sceneworld; m_log = logger; - ScriptConfigSource = ConfigSource.Configs[ScriptConfigSourceName]; + ScriptConfigSource = ConfigSource.Configs[ScriptEngineName]; - Log.Verbose("ScriptEngine", "DotNet & LSL ScriptEngine initializing"); + Log.Verbose(ScriptEngineName, "DotNet & LSL ScriptEngine initializing"); - //m_logger.Status("ScriptEngine", "InitializeEngine"); + //m_logger.Status(ScriptEngineName, "InitializeEngine"); // Create all objects we'll be using m_EventQueueManager = new EventQueueManager(this); m_EventManager = new EventManager(this, HookUpToServer); m_ScriptManager = newScriptManager; - //m_ScriptManager = new ScriptManager(this); - m_AppDomainManager = new AppDomainManager(ScriptConfigSource.GetInt("ScriptsPerAppDomain", 1)); - m_LSLLongCmdHandler = new LSLLongCmdHandler(this); + m_AppDomainManager = new AppDomainManager(this); + m_ASYNCLSLCommandManager = new AsyncLSLCommandManager(this); + m_MaintenanceThread = new MaintenanceThread(this); + + ReadConfig(); - ReReadConfigFileSeconds = ScriptConfigSource.GetInt("ReReadConfig", 0); // Should we iterate the region for scripts that needs starting? @@ -118,6 +120,26 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase { return this.m_EventManager; } + public void ReadConfig() + { +#if DEBUG + Log.Debug(ScriptEngineName, "Refreshing configuration for all modules"); +#endif + RefreshConfigFileSeconds = ScriptConfigSource.GetInt("RefreshConfig", 0); + + // Reload from disk + ConfigSource.Reload(); + // Create a new object (probably not necessary?) +// ScriptConfigSource = ConfigSource.Configs[ScriptEngineName]; + + if (m_EventQueueManager != null) m_EventQueueManager.ReadConfig(); + if (m_EventManager != null) m_EventManager.ReadConfig(); + if (m_ScriptManager != null) m_ScriptManager.ReadConfig(); + if (m_AppDomainManager != null) m_AppDomainManager.ReadConfig(); + if (m_ASYNCLSLCommandManager != null) m_ASYNCLSLCommandManager.ReadConfig(); + if (m_MaintenanceThread != null) m_MaintenanceThread.ReadConfig(); + + } #region IRegionModule @@ -134,7 +156,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase public string Name { - get { return "DotNetEngine"; } + get { return "Common." + ScriptEngineName; } } public bool IsSharedModule @@ -146,5 +168,15 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase #endregion + /// + /// 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; + } } \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/ScriptManager.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/ScriptManager.cs index ea87581..45cfced 100644 --- a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/ScriptManager.cs +++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/ScriptManager.cs @@ -57,12 +57,12 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase // This so that scripts starting or stopping will not slow down other theads or whole system. // [Serializable] - public abstract class ScriptManager + public abstract class ScriptManager : iScriptEngineFunctionModule { #region Declares private Thread scriptLoadUnloadThread; - private int scriptLoadUnloadThread_IdleSleepms = 100; + private int scriptLoadUnloadThread_IdleSleepms; private Queue LUQueue = new Queue(); @@ -95,6 +95,11 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase #endregion + public void ReadConfig() + { + scriptLoadUnloadThread_IdleSleepms = m_scriptEngine.ScriptConfigSource.GetInt("ScriptLoadUnloadLoopms", 30); + } + #region Object init/shutdown public ScriptEngineBase.ScriptEngine m_scriptEngine; @@ -102,6 +107,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase public ScriptManager(ScriptEngineBase.ScriptEngine scriptEngine) { m_scriptEngine = scriptEngine; + ReadConfig(); AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); scriptLoadUnloadThread = new Thread(ScriptLoadUnloadThreadLoop); scriptLoadUnloadThread.Name = "ScriptLoadUnloadThread"; @@ -238,7 +244,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase Console.WriteLine("ScriptEngine: Inside ExecuteEvent for event " + FunctionName); #endif // Execute a function in the script - //m_scriptEngine.Log.Verbose("ScriptEngine", "Executing Function localID: " + localID + ", itemID: " + itemID + ", FunctionName: " + FunctionName); + //m_scriptEngine.Log.Verbose(ScriptEngineName, "Executing Function localID: " + localID + ", itemID: " + itemID + ", FunctionName: " + FunctionName); //ScriptBaseInterface Script = (ScriptBaseInterface)GetScript(localID, itemID); IScript Script = GetScript(localID, itemID); if (Script == null) @@ -345,5 +351,16 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase } #endregion + + /// + /// 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; + } } \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/iScriptEngineFunctionModule.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/iScriptEngineFunctionModule.cs new file mode 100644 index 0000000..7539074 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/iScriptEngineFunctionModule.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase +{ + public interface iScriptEngineFunctionModule + { + void ReadConfig(); + bool PleaseShutdown { get; set; } + } +} \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/ScriptEngine.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/ScriptEngine.cs index de168b7..5ba37f7 100644 --- a/OpenSim/Region/ScriptEngine/DotNetEngine/ScriptEngine.cs +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/ScriptEngine.cs @@ -48,7 +48,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine return new ScriptManager(this); } - public override string ScriptConfigSourceName + public override string ScriptEngineName { get { return "ScriptEngine.DotNetEngine"; } } diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/ScriptManager.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/ScriptManager.cs index 5e1b537..9cad388 100644 --- a/OpenSim/Region/ScriptEngine/DotNetEngine/ScriptManager.cs +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/ScriptManager.cs @@ -124,7 +124,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine // Stop long command on script - m_scriptEngine.m_LSLLongCmdHandler.RemoveScript(localID, itemID); + m_scriptEngine.m_ASYNCLSLCommandManager.RemoveScript(localID, itemID); IScript LSLBC = GetScript(localID, itemID); if (LSLBC == null) diff --git a/OpenSim/Region/ScriptEngine/LSOEngine/ScriptEngine.cs b/OpenSim/Region/ScriptEngine/LSOEngine/ScriptEngine.cs index aac210b..49727c2 100644 --- a/OpenSim/Region/ScriptEngine/LSOEngine/ScriptEngine.cs +++ b/OpenSim/Region/ScriptEngine/LSOEngine/ScriptEngine.cs @@ -52,7 +52,7 @@ namespace OpenSim.Region.ScriptEngine.LSOEngine return new ScriptManager(this); } - public override string ScriptConfigSourceName + public override string ScriptEngineName { get { return "ScriptEngine.LSOEngine"; } } diff --git a/OpenSim/Region/ScriptEngine/LSOEngine/ScriptManager.cs b/OpenSim/Region/ScriptEngine/LSOEngine/ScriptManager.cs index de7b466..6664025 100644 --- a/OpenSim/Region/ScriptEngine/LSOEngine/ScriptManager.cs +++ b/OpenSim/Region/ScriptEngine/LSOEngine/ScriptManager.cs @@ -127,7 +127,7 @@ namespace OpenSim.Region.ScriptEngine.LSOEngine // Stop long command on script - m_scriptEngine.m_LSLLongCmdHandler.RemoveScript(localID, itemID); + m_scriptEngine.m_ASYNCLSLCommandManager.RemoveScript(localID, itemID); IScript LSLBC = GetScript(localID, itemID); if (LSLBC == null) diff --git a/bin/OpenSim.ini.example b/bin/OpenSim.ini.example index 9d664bf..94bacd1 100644 --- a/bin/OpenSim.ini.example +++ b/bin/OpenSim.ini.example @@ -123,6 +123,13 @@ shout_distance = 100 ; Same if you have 10 threads, then only 10 scripts can be run simultaneously. ; But because most scripts exit after their task, the threads are free to go on to the next script. +; Refresh ScriptEngine config options (these settings) every xx seconds +; 0 = Do not refresh +; Set it to number of seconds between refresh, for example 30. +; Will allow you to change ScriptEngine settings while server is running just by editing this file. +; For example to increase or decrease number of threads. +RefreshConfig=0 + ; Number of threads to use for script event execution ; Threads are shared across all regions NumberOfScriptThreads=2 @@ -136,6 +143,7 @@ ScriptThreadPriority=BelowNormal ; Number of threads will be * ; false: All regions share 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. +; *** This setting will not work until you restart OpenSim PrivateRegionThreads=false ; How long MAX should a script event be allowed to run (per event execution)? @@ -164,5 +172,14 @@ SleepTimeIfNoScriptExecutionMs=50 ; Each AppDomain has some memory overhead. But leaving dead scripts in memory also has memory overhead. ScriptsPerAppDomain=1 -; ReRead ScriptEngine config options how often? -ReReadConfig=0 +; Script loading / unloading sleep +; How long load/unload thread should sleep if there is nothing to do +; Higher value makes it respond slower when scripts are added/removed from prims +; But once active it will process all in queue before sleeping again +ScriptLoadUnloadLoopms=30 + +; Async LL command sleep +; If no async LL commands are waiting, how long should thread sleep before checking again +; Async LL commands are LSL-commands that causes an event to be fired back with result +AsyncLLCommandLoopms=50 + -- cgit v1.1