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.

afrisby
Tedd Hansen 2007-08-25 15:31:47 +00:00
parent 573fb3a609
commit 53be4774b3
7 changed files with 190 additions and 60 deletions

View File

@ -2,34 +2,40 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Reflection; using System.Reflection;
using System.Runtime.Remoting.Lifetime;
namespace OpenSim.Region.ScriptEngine.Common namespace OpenSim.Region.ScriptEngine.Common
{ {
public class Executor : MarshalByRefObject public class Executor : MarshalByRefObject
{ {
/* TODO: // Private instance for each script
*
* 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 IScript m_Script; private IScript m_Script;
private Dictionary<string, MethodInfo> Events = new Dictionary<string, MethodInfo>(); private Dictionary<string, MethodInfo> Events = new Dictionary<string, MethodInfo>();
private bool m_Running = true; private bool m_Running = true;
//private List<IScript> Scripts = new List<IScript>();
public Executor(IScript Script) public Executor(IScript Script)
{ {
m_Script = 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() public AppDomain GetAppDomain()
{ {
return AppDomain.CurrentDomain; return AppDomain.CurrentDomain;
@ -40,14 +46,6 @@ namespace OpenSim.Region.ScriptEngine.Common
// IMPORTANT: Types and MemberInfo-derived objects require a LOT of memory. // IMPORTANT: Types and MemberInfo-derived objects require a LOT of memory.
// Instead use RuntimeTypeHandle, RuntimeFieldHandle and RunTimeHandle (IntPtr) instead! // 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) if (m_Running == false)
{ {
// Script is inactive, do not execute! // Script is inactive, do not execute!
@ -97,6 +95,13 @@ namespace OpenSim.Region.ScriptEngine.Common
} }
} }
public void StopScript()
{
m_Running = false;
}
} }
} }

View File

@ -15,7 +15,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
{ {
public class AppDomainManager public class AppDomainManager
{ {
private int MaxScriptsPerAppDomain = 3; private int MaxScriptsPerAppDomain = 1;
/// <summary> /// <summary>
/// Internal list of all AppDomains /// Internal list of all AppDomains
/// </summary> /// </summary>
@ -59,7 +59,6 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
private AppDomainStructure GetFreeAppDomain() private AppDomainStructure GetFreeAppDomain()
{ {
Console.WriteLine("Finding free AppDomain"); Console.WriteLine("Finding free AppDomain");
FreeAppDomains(); // Outsite lock, has its own GetLock
lock (GetLock) lock (GetLock)
{ {
// Current full? // Current full?
@ -111,7 +110,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
/// <summary> /// <summary>
/// Unload appdomains that are full and have only dead scripts /// Unload appdomains that are full and have only dead scripts
/// </summary> /// </summary>
private void FreeAppDomains() private void UnloadAppDomains()
{ {
lock (FreeLock) lock (FreeLock)
{ {
@ -125,10 +124,18 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
// Is number of unloaded bigger or equal to number of loaded? // Is number of unloaded bigger or equal to number of loaded?
if (ads.ScriptsLoaded <= ads.ScriptsWaitingUnload) if (ads.ScriptsLoaded <= ads.ScriptsWaitingUnload)
{ {
Console.WriteLine("Found empty AppDomain, unloading");
// Remove from internal list // Remove from internal list
AppDomains.Remove(ads); AppDomains.Remove(ads);
#if DEBUG
long m = GC.GetTotalMemory(true);
#endif
// Unload // Unload
AppDomain.Unload(ads.CurrentAppDomain); AppDomain.Unload(ads.CurrentAppDomain);
#if DEBUG
Console.WriteLine("AppDomain unload freed " + (m - GC.GetTotalMemory(true)) + " bytes of memory");
#endif
} }
} }
} // foreach } // foreach
@ -142,20 +149,12 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
// Find next available AppDomain to put it in // Find next available AppDomain to put it in
AppDomainStructure FreeAppDomain = GetFreeAppDomain(); 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); Console.WriteLine("Loading into AppDomain: " + FileName);
LSL_BaseClass mbrt = (LSL_BaseClass)FreeAppDomain.CurrentAppDomain.CreateInstanceFromAndUnwrap(FileName, "SecondLife.Script"); 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));
Console.WriteLine("ScriptEngine AppDomainManager: is proxy={0}", RemotingServices.IsTransparentProxy(mbrt));
// Increase script count in tihs AppDomain
FreeAppDomain.ScriptsLoaded++; FreeAppDomain.ScriptsLoaded++;
//mbrt.Start();
return mbrt; return mbrt;
//return (LSL_BaseClass)mbrt;
} }
@ -168,6 +167,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
{ {
lock (FreeLock) lock (FreeLock)
{ {
Console.WriteLine("Stopping script in AppDomain");
// Check if it is current AppDomain // Check if it is current AppDomain
if (CurrentAD.CurrentAppDomain == ad) if (CurrentAD.CurrentAppDomain == ad)
{ {
@ -181,15 +181,16 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
{ {
if (ads.CurrentAppDomain == ad) if (ads.CurrentAppDomain == ad)
{ {
// Found it - messy code to increase structure // Found it
//AppDomainStructure ads2 = ads;
ads.ScriptsWaitingUnload++; ads.ScriptsWaitingUnload++;
//AppDomains.Remove(ads); break;
//AppDomains.Add(ads2);
return;
} }
} // foreach } // foreach
} // lock } // lock
UnloadAppDomains(); // Outsite lock, has its own GetLock
} }

View File

@ -5,14 +5,33 @@ using OpenSim.Region.ScriptEngine.DotNetEngine.Compiler;
using OpenSim.Region.ScriptEngine.Common; using OpenSim.Region.ScriptEngine.Common;
using System.Threading; using System.Threading;
using System.Reflection; using System.Reflection;
using System.Runtime.Remoting.Lifetime;
namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.LSL namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.LSL
{ {
public class LSL_BaseClass : MarshalByRefObject, LSL_BuiltIn_Commands_Interface, IScript 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; private Executor m_Exec;
public Executor Exec { public Executor Exec
{
get get
{ {
if (m_Exec == null) if (m_Exec == null)

View File

@ -7,6 +7,7 @@ using OpenSim.Region.Environment.Scenes.Scripting;
using OpenSim.Region.ScriptEngine.DotNetEngine.Compiler; using OpenSim.Region.ScriptEngine.DotNetEngine.Compiler;
using OpenSim.Region.ScriptEngine.Common; using OpenSim.Region.ScriptEngine.Common;
using OpenSim.Framework.Console; using OpenSim.Framework.Console;
using System.Runtime.Remoting.Lifetime;
namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler
{ {
@ -35,6 +36,23 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler
return m_state; 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 public Scene World
{ {
get { return m_manager.World; } get { return m_manager.World; }

View File

@ -62,7 +62,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
{ {
// Add to queue for all scripts in ObjectID object // Add to queue for all scripts in ObjectID object
//myScriptEngine.m_logger.Verbose("ScriptEngine", "EventManager Event: touch_start"); //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 }); myScriptEngine.myEventQueueManager.AddToObjectQueue(localID, "touch_start", new object[] { (int)1 });
} }
public void OnRezScript(uint localID, LLUUID itemID, string script) public void OnRezScript(uint localID, LLUUID itemID, string script)
@ -85,6 +85,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
// Path.Combine("ScriptEngines", "Default.lsl"), // Path.Combine("ScriptEngines", "Default.lsl"),
// new OpenSim.Region.Environment.Scenes.Scripting.NullScriptHost() // new OpenSim.Region.Environment.Scenes.Scripting.NullScriptHost()
//); //);
Console.WriteLine("OnRemoveScript localID: " + localID + " LLUID: " + itemID.ToString());
myScriptEngine.myScriptManager.StopScript( myScriptEngine.myScriptManager.StopScript(
localID, localID,
itemID itemID
@ -96,7 +97,8 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
// These needs to be hooked up to OpenSim during init of this class // These needs to be hooked up to OpenSim during init of this class
// then queued in EventQueueManager. // then queued in EventQueueManager.
// When queued in EventQueueManager they need to be LSL compatible (name and params) // 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 state_exit() { }
//public void touch_start() { } //public void touch_start() { }
public void touch() { } public void touch() { }

View File

@ -243,7 +243,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
// Do we have any scripts in this object at all? If not, return // Do we have any scripts in this object at all? If not, return
if (myScriptEngine.myScriptManager.Scripts.ContainsKey(localID) == false) 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; return;
} }

View File

@ -50,11 +50,82 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
public class ScriptManager public class ScriptManager
{ {
private Thread ScriptLoadUnloadThread;
private int ScriptLoadUnloadThread_IdleSleepms = 100;
private Queue<LoadStruct> LoadQueue = new Queue<LoadStruct>();
private Queue<UnloadStruct> UnloadQueue = new Queue<UnloadStruct>();
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; private ScriptEngine m_scriptEngine;
public ScriptManager(ScriptEngine scriptEngine) public ScriptManager(ScriptEngine scriptEngine)
{ {
m_scriptEngine = scriptEngine; m_scriptEngine = scriptEngine;
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); 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) private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
@ -145,9 +216,30 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
/// <param name="itemID"></param> /// <param name="itemID"></param>
/// <param name="localID"></param> /// <param name="localID"></param>
public void StartScript(uint localID, LLUUID itemID, string Script) 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);
}
/// <summary>
/// Disables and unloads a script
/// </summary>
/// <param name="localID"></param>
/// <param name="itemID"></param>
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(); //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. // We will initialize and start the script.
// It will be up to the script itself to hook up the correct events. // 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(); OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.LSL.Compiler LSLCompiler = new OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.LSL.Compiler();
// Compile (We assume LSL) // Compile (We assume LSL)
FileName = LSLCompiler.CompileFromLSLText(Script); FileName = LSLCompiler.CompileFromLSLText(Script);
m_scriptEngine.Log.Verbose("ScriptEngine", "Compilation of " + FileName + " done"); Console.WriteLine("Compilation of " + FileName + " done");
// * Insert yield into code // * Insert yield into code
FileName = ProcessYield(FileName); FileName = ProcessYield(FileName);
//OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.LSO.LSL_BaseClass Script = LoadAndInitAssembly(FreeAppDomain, FileName); #if DEBUG
//OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.LSL.LSL_BaseClass Script = LoadAndInitAssembly(FreeAppDomain, FileName, localID);
long before; long before;
before = GC.GetTotalMemory(false); before = GC.GetTotalMemory(false);
#endif
LSL_BaseClass CompiledScript = m_scriptEngine.myAppDomainManager.LoadScript(FileName); LSL_BaseClass CompiledScript = m_scriptEngine.myAppDomainManager.LoadScript(FileName);
#if DEBUG
Console.WriteLine("Script " + itemID + " occupies {0} bytes", GC.GetTotalMemory(false) - before); Console.WriteLine("Script " + itemID + " occupies {0} bytes", GC.GetTotalMemory(false) - before);
//before = GC.GetTotalMemory(false); #endif
// Add it to our script memstruct
//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);
SetScript(localID, itemID, CompiledScript); SetScript(localID, itemID, CompiledScript);
// We need to give (untrusted) assembly a private instance of BuiltIns // 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. // 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)); 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 // Stop script
Console.WriteLine("Stop script localID: " + localID + " LLUID: " + itemID.ToString());
// Get AppDomain // Get AppDomain
AppDomain ad = GetScript(localID, itemID).Exec.GetAppDomain(); AppDomain ad = GetScript(localID, itemID).Exec.GetAppDomain();
@ -235,7 +320,7 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine
{ {
// Execute a function in the script // 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); LSL_BaseClass Script = m_scriptEngine.myScriptManager.GetScript(localID, itemID);
// Must be done in correct AppDomain, so leaving it up to the script itself // Must be done in correct AppDomain, so leaving it up to the script itself