From 53be4774b32f6736373c1364c3bd659c977fbb4e Mon Sep 17 00:00:00 2001 From: Tedd Hansen Date: Sat, 25 Aug 2007 15:31:47 +0000 Subject: Scripts no longer crash sim after 5 minutes (override InitializeLifetimeService). Loading/Unloading of scripts are now handled in separate thread so server is no delayed because of this. Each script is loaded into a single AppDomain (temporary test for script unload, eats ~15KB more memory for each script). Unload of scripts has been verified to free up memory. --- OpenSim/Region/ScriptEngine/Common/Executor.cs | 43 +++---- .../ScriptEngine/DotNetEngine/AppDomainManager.cs | 35 +++--- .../DotNetEngine/Compiler/LSL/LSL_BaseClass.cs | 23 +++- .../Compiler/Server_API/LSL_BuiltIn_Commands.cs | 18 +++ .../ScriptEngine/DotNetEngine/EventManager.cs | 6 +- .../ScriptEngine/DotNetEngine/EventQueueManager.cs | 2 +- .../ScriptEngine/DotNetEngine/ScriptManager.cs | 123 +++++++++++++++++---- 7 files changed, 190 insertions(+), 60 deletions(-) (limited to 'OpenSim/Region') diff --git a/OpenSim/Region/ScriptEngine/Common/Executor.cs b/OpenSim/Region/ScriptEngine/Common/Executor.cs index 148ae0f..cadd55c 100644 --- a/OpenSim/Region/ScriptEngine/Common/Executor.cs +++ b/OpenSim/Region/ScriptEngine/Common/Executor.cs @@ -2,34 +2,40 @@ using System; using System.Collections.Generic; using System.Text; using System.Reflection; +using System.Runtime.Remoting.Lifetime; namespace OpenSim.Region.ScriptEngine.Common { public class Executor : MarshalByRefObject { - /* TODO: - * - * Needs to be common for all AppDomains - share memory too? - * Needs to have an instance in each AppDomain, and some way of referring it. - * Need to know what AppDomain a script is in so we know where to find our instance. - * - */ + // Private instance for each script private IScript m_Script; private Dictionary Events = new Dictionary(); private bool m_Running = true; - + //private List Scripts = new List(); public Executor(IScript Script) { m_Script = Script; - } - public void StopScript() + // Object never expires + public override Object InitializeLifetimeService() { - m_Running = false; + Console.WriteLine("Executor: InitializeLifetimeService()"); + // return null; + ILease lease = (ILease)base.InitializeLifetimeService(); + + if (lease.CurrentState == LeaseState.Initial) + { + lease.InitialLeaseTime = TimeSpan.Zero; // TimeSpan.FromMinutes(1); +// lease.SponsorshipTimeout = TimeSpan.FromMinutes(2); +// lease.RenewOnCallTime = TimeSpan.FromSeconds(2); + } + return lease; } + public AppDomain GetAppDomain() { return AppDomain.CurrentDomain; @@ -40,14 +46,6 @@ namespace OpenSim.Region.ScriptEngine.Common // IMPORTANT: Types and MemberInfo-derived objects require a LOT of memory. // Instead use RuntimeTypeHandle, RuntimeFieldHandle and RunTimeHandle (IntPtr) instead! - //foreach (MemberInfo mi in this.GetType().GetMembers()) - //{ - //if (mi.ToString().ToLower().Contains("default")) - //{ - // Console.WriteLine("Member found: " + mi.ToString()); - //} - //} - if (m_Running == false) { // Script is inactive, do not execute! @@ -97,6 +95,13 @@ namespace OpenSim.Region.ScriptEngine.Common } } + + public void StopScript() + { + m_Running = false; + } + + } } diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/AppDomainManager.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/AppDomainManager.cs index 3319783..63e1844 100644 --- a/OpenSim/Region/ScriptEngine/DotNetEngine/AppDomainManager.cs +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/AppDomainManager.cs @@ -15,7 +15,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine { public class AppDomainManager { - private int MaxScriptsPerAppDomain = 3; + private int MaxScriptsPerAppDomain = 1; /// /// Internal list of all AppDomains /// @@ -59,7 +59,6 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine private AppDomainStructure GetFreeAppDomain() { Console.WriteLine("Finding free AppDomain"); - FreeAppDomains(); // Outsite lock, has its own GetLock lock (GetLock) { // Current full? @@ -111,7 +110,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine /// /// Unload appdomains that are full and have only dead scripts /// - private void FreeAppDomains() + private void UnloadAppDomains() { lock (FreeLock) { @@ -125,10 +124,18 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine // 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 + + 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"); +#endif } } } // foreach @@ -142,20 +149,12 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine // Find next available AppDomain to put it in AppDomainStructure FreeAppDomain = GetFreeAppDomain(); - //if (FreeAppDomain == null) Console.WriteLine("FreeAppDomain == null"); - //if (FreeAppDomain.CurrentAppDomain == null) Console.WriteLine("FreeAppDomain.CurrentAppDomain == null"); Console.WriteLine("Loading into AppDomain: " + FileName); LSL_BaseClass mbrt = (LSL_BaseClass)FreeAppDomain.CurrentAppDomain.CreateInstanceFromAndUnwrap(FileName, "SecondLife.Script"); - //Type mytype = mbrt.GetType(); - Console.WriteLine("ScriptEngine AppDomainManager: is proxy={0}", RemotingServices.IsTransparentProxy(mbrt)); - - // Increase script count in tihs AppDomain + //Console.WriteLine("ScriptEngine AppDomainManager: is proxy={0}", RemotingServices.IsTransparentProxy(mbrt)); FreeAppDomain.ScriptsLoaded++; - //mbrt.Start(); return mbrt; - //return (LSL_BaseClass)mbrt; - } @@ -168,6 +167,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine { lock (FreeLock) { + Console.WriteLine("Stopping script in AppDomain"); // Check if it is current AppDomain if (CurrentAD.CurrentAppDomain == ad) { @@ -181,15 +181,16 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine { if (ads.CurrentAppDomain == ad) { - // Found it - messy code to increase structure - //AppDomainStructure ads2 = ads; + // Found it ads.ScriptsWaitingUnload++; - //AppDomains.Remove(ads); - //AppDomains.Add(ads2); - return; + break; } } // foreach } // lock + + UnloadAppDomains(); // Outsite lock, has its own GetLock + + } diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/LSL/LSL_BaseClass.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/LSL/LSL_BaseClass.cs index bfb8913..cb0f9ba 100644 --- a/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/LSL/LSL_BaseClass.cs +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/LSL/LSL_BaseClass.cs @@ -5,14 +5,33 @@ using OpenSim.Region.ScriptEngine.DotNetEngine.Compiler; using OpenSim.Region.ScriptEngine.Common; using System.Threading; using System.Reflection; - +using System.Runtime.Remoting.Lifetime; namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.LSL { public class LSL_BaseClass : MarshalByRefObject, LSL_BuiltIn_Commands_Interface, IScript { + + // Object never expires + public override Object InitializeLifetimeService() + { + Console.WriteLine("LSL_BaseClass: InitializeLifetimeService()"); + // return null; + ILease lease = (ILease)base.InitializeLifetimeService(); + + if (lease.CurrentState == LeaseState.Initial) + { + lease.InitialLeaseTime = TimeSpan.Zero; // TimeSpan.FromMinutes(1); + //lease.SponsorshipTimeout = TimeSpan.FromMinutes(2); + //lease.RenewOnCallTime = TimeSpan.FromSeconds(2); + } + return lease; + } + + private Executor m_Exec; - public Executor Exec { + public Executor Exec + { get { if (m_Exec == null) diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/Server_API/LSL_BuiltIn_Commands.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/Server_API/LSL_BuiltIn_Commands.cs index ca51542..187ac59 100644 --- a/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/Server_API/LSL_BuiltIn_Commands.cs +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/Server_API/LSL_BuiltIn_Commands.cs @@ -7,6 +7,7 @@ using OpenSim.Region.Environment.Scenes.Scripting; using OpenSim.Region.ScriptEngine.DotNetEngine.Compiler; using OpenSim.Region.ScriptEngine.Common; using OpenSim.Framework.Console; +using System.Runtime.Remoting.Lifetime; namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler { @@ -35,6 +36,23 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler return m_state; } + // Object never expires + public override Object InitializeLifetimeService() + { + Console.WriteLine("LSL_BuiltIn_Commands: InitializeLifetimeService()"); + // return null; + ILease lease = (ILease)base.InitializeLifetimeService(); + + if (lease.CurrentState == LeaseState.Initial) + { + lease.InitialLeaseTime = TimeSpan.Zero; // TimeSpan.FromMinutes(1); + // lease.SponsorshipTimeout = TimeSpan.FromMinutes(2); + // lease.RenewOnCallTime = TimeSpan.FromSeconds(2); + } + return lease; + } + + public Scene World { get { return m_manager.World; } diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/EventManager.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/EventManager.cs index ea5500a..986d333 100644 --- a/OpenSim/Region/ScriptEngine/DotNetEngine/EventManager.cs +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/EventManager.cs @@ -62,7 +62,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine { // Add to queue for all scripts in ObjectID object //myScriptEngine.m_logger.Verbose("ScriptEngine", "EventManager Event: touch_start"); - Console.WriteLine("touch_start localID: " + localID); + //Console.WriteLine("touch_start localID: " + localID); myScriptEngine.myEventQueueManager.AddToObjectQueue(localID, "touch_start", new object[] { (int)1 }); } public void OnRezScript(uint localID, LLUUID itemID, string script) @@ -85,6 +85,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine // Path.Combine("ScriptEngines", "Default.lsl"), // new OpenSim.Region.Environment.Scenes.Scripting.NullScriptHost() //); + Console.WriteLine("OnRemoveScript localID: " + localID + " LLUID: " + itemID.ToString()); myScriptEngine.myScriptManager.StopScript( localID, itemID @@ -96,7 +97,8 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine // These needs to be hooked up to OpenSim during init of this class // then queued in EventQueueManager. // When queued in EventQueueManager they need to be LSL compatible (name and params) - public void state_entry() { } + + //public void state_entry() { } // public void state_exit() { } //public void touch_start() { } public void touch() { } diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/EventQueueManager.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/EventQueueManager.cs index 012e60d..1cab01e 100644 --- a/OpenSim/Region/ScriptEngine/DotNetEngine/EventQueueManager.cs +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/EventQueueManager.cs @@ -243,7 +243,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine // Do we have any scripts in this object at all? If not, return if (myScriptEngine.myScriptManager.Scripts.ContainsKey(localID) == false) { - Console.WriteLine("Event \"" + FunctionName + "\" for localID: " + localID + ". No scripts found on this localID."); + //Console.WriteLine("Event \"" + FunctionName + "\" for localID: " + localID + ". No scripts found on this localID."); return; } diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/ScriptManager.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/ScriptManager.cs index cadae05..9621e56 100644 --- a/OpenSim/Region/ScriptEngine/DotNetEngine/ScriptManager.cs +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/ScriptManager.cs @@ -50,11 +50,82 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine public class ScriptManager { + private Thread ScriptLoadUnloadThread; + private int ScriptLoadUnloadThread_IdleSleepms = 100; + private Queue LoadQueue = new Queue(); + private Queue UnloadQueue = new Queue(); + private struct LoadStruct + { + public uint localID; + public LLUUID itemID; + public string Script; + } + private struct UnloadStruct + { + public uint localID; + public LLUUID itemID; + } + private ScriptEngine m_scriptEngine; public ScriptManager(ScriptEngine scriptEngine) { m_scriptEngine = scriptEngine; AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); + ScriptLoadUnloadThread = new Thread(ScriptLoadUnloadThreadLoop); + ScriptLoadUnloadThread.Name = "ScriptLoadUnloadThread"; + ScriptLoadUnloadThread.IsBackground = true; + ScriptLoadUnloadThread.Priority = ThreadPriority.BelowNormal; + ScriptLoadUnloadThread.Start(); + + } + ~ScriptManager () + { + // Abort load/unload thread + try + { + if (ScriptLoadUnloadThread != null) + { + if (ScriptLoadUnloadThread.IsAlive == true) + { + ScriptLoadUnloadThread.Abort(); + ScriptLoadUnloadThread.Join(); + } + } + } + catch + { + } + } + private void ScriptLoadUnloadThreadLoop() + { + try + { + while (true) + { + if (LoadQueue.Count == 0 && UnloadQueue.Count == 0) + Thread.Sleep(ScriptLoadUnloadThread_IdleSleepms); + + if (LoadQueue.Count > 0) + { + LoadStruct item = LoadQueue.Dequeue(); + _StartScript(item.localID, item.itemID, item.Script); + } + + if (UnloadQueue.Count > 0) + { + UnloadStruct item = UnloadQueue.Dequeue(); + _StopScript(item.localID, item.itemID); + } + + + + } + } + catch (ThreadAbortException tae) + { + // Expected + } + } private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) @@ -146,8 +217,29 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine /// public void StartScript(uint localID, LLUUID itemID, string Script) { + LoadStruct ls = new LoadStruct(); + ls.localID = localID; + ls.itemID = itemID; + ls.Script = Script; + LoadQueue.Enqueue(ls); + } + /// + /// Disables and unloads a script + /// + /// + /// + public void StopScript(uint localID, LLUUID itemID) + { + UnloadStruct ls = new UnloadStruct(); + ls.localID = localID; + ls.itemID = itemID; + UnloadQueue.Enqueue(ls); + } + + private void _StartScript(uint localID, LLUUID itemID, string Script) + { //IScriptHost root = host.GetRoot(); - m_scriptEngine.Log.Verbose("ScriptEngine", "ScriptManager StartScript: localID: " + localID + ", itemID: " + itemID); + Console.WriteLine("ScriptManager StartScript: localID: " + localID + ", itemID: " + itemID); // We will initialize and start the script. // It will be up to the script itself to hook up the correct events. @@ -161,32 +253,23 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.LSL.Compiler LSLCompiler = new OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.LSL.Compiler(); // Compile (We assume LSL) FileName = LSLCompiler.CompileFromLSLText(Script); - m_scriptEngine.Log.Verbose("ScriptEngine", "Compilation of " + FileName + " done"); + Console.WriteLine("Compilation of " + FileName + " done"); // * Insert yield into code FileName = ProcessYield(FileName); - //OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.LSO.LSL_BaseClass Script = LoadAndInitAssembly(FreeAppDomain, FileName); - - //OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.LSL.LSL_BaseClass Script = LoadAndInitAssembly(FreeAppDomain, FileName, localID); - +#if DEBUG long before; before = GC.GetTotalMemory(false); +#endif LSL_BaseClass CompiledScript = m_scriptEngine.myAppDomainManager.LoadScript(FileName); +#if DEBUG Console.WriteLine("Script " + itemID + " occupies {0} bytes", GC.GetTotalMemory(false) - before); - //before = GC.GetTotalMemory(false); - +#endif - //Script = m_scriptEngine.myAppDomainManager.LoadScript(FileName); - //Console.WriteLine("Script occupies {0} bytes", GC.GetTotalMemory(true) - before); - //before = GC.GetTotalMemory(true); - //Script = m_scriptEngine.myAppDomainManager.LoadScript(FileName); - //Console.WriteLine("Script occupies {0} bytes", GC.GetTotalMemory(true) - before); - - - // Add it to our temporary active script keeper - //Scripts.Add(FullitemID, Script); + // Add it to our script memstruct SetScript(localID, itemID, CompiledScript); + // We need to give (untrusted) assembly a private instance of BuiltIns // this private copy will contain Read-Only FullitemID so that it can bring that on to the server whenever needed. LSL_BuiltIn_Commands LSLB = new LSL_BuiltIn_Commands(this, World.GetSceneObjectPart(localID)); @@ -202,9 +285,11 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine } - public void StopScript(uint localID, LLUUID itemID) + + private void _StopScript(uint localID, LLUUID itemID) { // Stop script + Console.WriteLine("Stop script localID: " + localID + " LLUID: " + itemID.ToString()); // Get AppDomain AppDomain ad = GetScript(localID, itemID).Exec.GetAppDomain(); @@ -235,7 +320,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine { // Execute a function in the script - m_scriptEngine.Log.Verbose("ScriptEngine", "Executing Function localID: " + localID + ", itemID: " + itemID + ", FunctionName: " + FunctionName); + //m_scriptEngine.Log.Verbose("ScriptEngine", "Executing Function localID: " + localID + ", itemID: " + itemID + ", FunctionName: " + FunctionName); LSL_BaseClass Script = m_scriptEngine.myScriptManager.GetScript(localID, itemID); // Must be done in correct AppDomain, so leaving it up to the script itself -- cgit v1.1