From 134f86e8d5c414409631b25b8c6f0ee45fbd8631 Mon Sep 17 00:00:00 2001 From: David Walter Seikel Date: Thu, 3 Nov 2016 21:44:39 +1000 Subject: Initial update to OpenSim 0.8.2.1 source code. --- .../ScriptEngine/Shared/Instance/ScriptInstance.cs | 806 +++++++++++++-------- 1 file changed, 488 insertions(+), 318 deletions(-) (limited to 'OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs') diff --git a/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs b/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs index 01a5e34..fa6e6fc 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs @@ -51,6 +51,7 @@ using OpenSim.Region.ScriptEngine.Shared.Api.Runtime; using OpenSim.Region.ScriptEngine.Shared.ScriptBase; using OpenSim.Region.ScriptEngine.Shared.CodeTools; using OpenSim.Region.ScriptEngine.Interfaces; +using System.Diagnostics; namespace OpenSim.Region.ScriptEngine.Shared.Instance { @@ -58,6 +59,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + public bool StatePersistedHere { get { return m_AttachedAvatar == UUID.Zero; } } + /// /// The current work item if an event for this script is running or waiting to run, /// @@ -71,14 +74,16 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance private bool m_TimerQueued; private DateTime m_EventStart; private bool m_InEvent; - private string m_Assembly; + private string m_assemblyPath; + private string m_dataPath; private string m_CurrentEvent = String.Empty; private bool m_InSelfDelete; private int m_MaxScriptQueue; - private bool m_SaveState = true; + private bool m_SaveState; private int m_ControlEventsInQueue; private int m_LastControlLevel; private bool m_CollisionInQueue; + private bool m_StateChangeInProgress; // The following is for setting a minimum delay between events private double m_minEventDelay; @@ -95,6 +100,9 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance public int DebugLevel { get; set; } + public WaitHandle CoopWaitHandle { get; private set; } + public Stopwatch ExecutionTimer { get; private set; } + public Dictionary, KeyValuePair> LineMap { get; set; } private Dictionary m_Apis = new Dictionary(); @@ -121,7 +129,18 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance } } - public bool Running { get; set; } + public bool Running + { + get { return m_running; } + + set + { + m_running = value; + if (m_running) + StayStopped = false; + } + } + private bool m_running; public bool Suspended { @@ -153,23 +172,27 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance public string State { get; set; } + public bool StayStopped { get; set; } + public IScriptEngine Engine { get; private set; } public UUID AppDomain { get; set; } + public SceneObjectPart Part { get; private set; } + public string PrimName { get; private set; } public string ScriptName { get; private set; } public UUID ItemID { get; private set; } - public UUID ObjectID { get; private set; } + public UUID ObjectID { get { return Part.UUID; } } - public uint LocalID { get; private set; } + public uint LocalID { get { return Part.LocalId; } } - public UUID RootObjectID { get; private set; } + public UUID RootObjectID { get { return Part.ParentGroup.UUID; } } - public uint RootLocalID { get; private set; } + public uint RootLocalID { get { return Part.ParentGroup.LocalId; } } public UUID AssetID { get; private set; } @@ -192,83 +215,92 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance public DateTime TimeStarted { get; private set; } - public long MeasurementPeriodTickStart { get; private set; } + public MetricsCollectorTime ExecutionTime { get; private set; } - public long MeasurementPeriodExecutionTime { get; private set; } + private static readonly int MeasurementWindow = 30 * 1000; // show the *recent* time used by the script, to find currently active scripts - public static readonly long MaxMeasurementPeriod = 30 * TimeSpan.TicksPerMinute; + private bool m_coopTermination; + + private EventWaitHandle m_coopSleepHandle; public void ClearQueue() { m_TimerQueued = false; + m_StateChangeInProgress = false; EventQueue.Clear(); } - public ScriptInstance(IScriptEngine engine, SceneObjectPart part, - UUID itemID, UUID assetID, string assembly, - AppDomain dom, string primName, string scriptName, - int startParam, bool postOnRez, StateSource stateSource, - int maxScriptQueue) + public ScriptInstance( + IScriptEngine engine, SceneObjectPart part, TaskInventoryItem item, + int startParam, bool postOnRez, + int maxScriptQueue) { State = "default"; EventQueue = new Queue(32); + ExecutionTimer = new Stopwatch(); Engine = engine; - LocalID = part.LocalId; - ObjectID = part.UUID; - RootLocalID = part.ParentGroup.LocalId; - RootObjectID = part.ParentGroup.UUID; - ItemID = itemID; - AssetID = assetID; - PrimName = primName; - ScriptName = scriptName; - m_Assembly = assembly; + Part = part; + ScriptTask = item; + + // This is currently only here to allow regression tests to get away without specifying any inventory + // item when they are testing script logic that doesn't require an item. + if (ScriptTask != null) + { + ScriptName = ScriptTask.Name; + ItemID = ScriptTask.ItemID; + AssetID = ScriptTask.AssetID; + } + + PrimName = part.ParentGroup.Name; StartParam = startParam; m_MaxScriptQueue = maxScriptQueue; - m_stateSource = stateSource; m_postOnRez = postOnRez; - m_AttachedAvatar = part.ParentGroup.AttachedAvatar; - m_RegionID = part.ParentGroup.Scene.RegionInfo.RegionID; + m_AttachedAvatar = Part.ParentGroup.AttachedAvatar; + m_RegionID = Part.ParentGroup.Scene.RegionInfo.RegionID; - if (part != null) - { - lock (part.TaskInventory) - { - if (part.TaskInventory.ContainsKey(ItemID)) - { - ScriptTask = part.TaskInventory[ItemID]; - } - } - } + m_SaveState = StatePersistedHere; + + ExecutionTime = new MetricsCollectorTime(MeasurementWindow, 10); + +// m_log.DebugFormat( +// "[SCRIPT INSTANCE]: Instantiated script instance {0} (id {1}) in part {2} (id {3}) in object {4} attached avatar {5} in {6}", +// ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, m_AttachedAvatar, Engine.World.Name); + } + + /// + /// Load the script from an assembly into an AppDomain. + /// + /// + /// + /// + /// Path for all script associated data (state, etc.). In a multi-region set up + /// with all scripts loading into the same AppDomain this may not be the same place as the DLL itself. + /// + /// + /// false if load failed, true if suceeded + public bool Load( + IScript script, EventWaitHandle coopSleepHandle, string assemblyPath, + string dataPath, StateSource stateSource, bool coopTermination) + { + m_Script = script; + m_coopSleepHandle = coopSleepHandle; + m_assemblyPath = assemblyPath; + m_dataPath = dataPath; + m_stateSource = stateSource; + m_coopTermination = coopTermination; + + if (m_coopTermination) + CoopWaitHandle = coopSleepHandle; + else + CoopWaitHandle = null; ApiManager am = new ApiManager(); foreach (string api in am.GetApis()) { m_Apis[api] = am.CreateApi(api); - m_Apis[api].Initialize(engine, part, ScriptTask); - } - - try - { - if (dom != System.AppDomain.CurrentDomain) - m_Script = (IScript)dom.CreateInstanceAndUnwrap( - Path.GetFileNameWithoutExtension(assembly), - "SecondLife.Script"); - else - m_Script = (IScript)Assembly.Load( - Path.GetFileNameWithoutExtension(assembly)).CreateInstance( - "SecondLife.Script"); - - //ILease lease = (ILease)RemotingServices.GetLifetimeService(m_Script as ScriptBaseClass); - //RemotingServices.GetLifetimeService(m_Script as ScriptBaseClass); -// lease.Register(this); - } - catch (Exception e) - { - m_log.ErrorFormat( - "[SCRIPT INSTANCE]: Error loading assembly {0}. Exception {1}{2}", - assembly, e.Message, e.StackTrace); + m_Apis[api].Initialize(Engine, Part, ScriptTask); } try @@ -278,26 +310,28 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance m_Script.InitApi(kv.Key, kv.Value); } -// // m_log.Debug("[Script] Script instance created"); + // // m_log.Debug("[Script] Script instance created"); - part.SetScriptEvents(ItemID, - (int)m_Script.GetStateEventFlags(State)); + Part.SetScriptEvents(ItemID, (int)m_Script.GetStateEventFlags(State)); } catch (Exception e) { m_log.ErrorFormat( - "[SCRIPT INSTANCE]: Error loading script instance from assembly {0}. Exception {1}{2}", - assembly, e.Message, e.StackTrace); + "[SCRIPT INSTANCE]: Not starting script {0} (id {1}) in part {2} (id {3}) in object {4} in {5}. Error initializing script instance. Exception {6}{7}", + ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, e.Message, e.StackTrace); - return; + return false; } - m_SaveState = true; + // For attachments, XEngine saves the state into a .state file when XEngine.SetXMLState() is called. + string savedState = Path.Combine(m_dataPath, ItemID.ToString() + ".state"); - string savedState = Path.Combine(Path.GetDirectoryName(assembly), - ItemID.ToString() + ".state"); if (File.Exists(savedState)) { + // m_log.DebugFormat( + // "[SCRIPT INSTANCE]: Found state for script {0} for {1} ({2}) at {3} in {4}", + // ItemID, savedState, Part.Name, Part.ParentGroup.Name, Part.ParentGroup.Scene.Name); + string xml = String.Empty; try @@ -317,13 +351,13 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance ScriptSerializer.Deserialize(xml, this); AsyncCommandManager.CreateFromData(Engine, - LocalID, ItemID, ObjectID, - PluginData); + LocalID, ItemID, ObjectID, + PluginData); -// m_log.DebugFormat("[Script] Successfully retrieved state for script {0}.{1}", PrimName, m_ScriptName); + // m_log.DebugFormat("[Script] Successfully retrieved state for script {0}.{1}", PrimName, m_ScriptName); - part.SetScriptEvents(ItemID, - (int)m_Script.GetStateEventFlags(State)); + Part.SetScriptEvents(ItemID, + (int)m_Script.GetStateEventFlags(State)); if (!Running) m_startOnInit = false; @@ -338,29 +372,33 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance m_SaveState = false; m_startedFromSavedState = true; } + + // If this script is in an attachment then we no longer need the state file. + if (!StatePersistedHere) + RemoveState(); } - else - { - m_log.WarnFormat( - "[SCRIPT INSTANCE]: Unable to load script state file {0} for script {1} {2} in {3} {4} (assembly {5}). Memory limit exceeded", - savedState, ScriptName, ItemID, PrimName, ObjectID, assembly); - } + // else + // { + // m_log.WarnFormat( + // "[SCRIPT INSTANCE]: Not starting script {0} (id {1}) in part {2} (id {3}) in object {4} in {5}. Unable to load script state file {6}. Memory limit exceeded.", + // ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, savedState); + // } } catch (Exception e) { - m_log.ErrorFormat( - "[SCRIPT INSTANCE]: Unable to load script state file {0} for script {1} {2} in {3} {4} (assembly {5}). XML is {6}. Exception {7}{8}", - savedState, ScriptName, ItemID, PrimName, ObjectID, assembly, xml, e.Message, e.StackTrace); + m_log.ErrorFormat( + "[SCRIPT INSTANCE]: Not starting script {0} (id {1}) in part {2} (id {3}) in object {4} in {5}. Unable to load script state file {6}. XML is {7}. Exception {8}{9}", + ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, savedState, xml, e.Message, e.StackTrace); } } -// else -// { -// ScenePresence presence = Engine.World.GetScenePresence(part.OwnerID); + // else + // { + // m_log.DebugFormat( + // "[SCRIPT INSTANCE]: Did not find state for script {0} for {1} ({2}) at {3} in {4}", + // ItemID, savedState, Part.Name, Part.ParentGroup.Name, Part.ParentGroup.Scene.Name); + // } -// if (presence != null && (!postOnRez)) -// presence.ControllingClient.SendAgentAlertMessage("Compile successful", false); - -// } + return true; } public void Init() @@ -418,33 +456,27 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance PostEvent(new EventParams("attach", new object[] { new LSL_Types.LSLString(m_AttachedAvatar.ToString()) }, new DetectParams[0])); } - } } private void ReleaseControls() { - SceneObjectPart part = Engine.World.GetSceneObjectPart(LocalID); - - if (part != null) + int permsMask; + UUID permsGranter; + lock (Part.TaskInventory) { - int permsMask; - UUID permsGranter; - lock (part.TaskInventory) - { - if (!part.TaskInventory.ContainsKey(ItemID)) - return; + if (!Part.TaskInventory.ContainsKey(ItemID)) + return; - permsGranter = part.TaskInventory[ItemID].PermsGranter; - permsMask = part.TaskInventory[ItemID].PermsMask; - } + permsGranter = Part.TaskInventory[ItemID].PermsGranter; + permsMask = Part.TaskInventory[ItemID].PermsMask; + } - if ((permsMask & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) != 0) - { - ScenePresence presence = Engine.World.GetScenePresence(permsGranter); - if (presence != null) - presence.UnRegisterControlEventsToScript(LocalID, ItemID); - } + if ((permsMask & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) != 0) + { + ScenePresence presence = Engine.World.GetScenePresence(permsGranter); + if (presence != null) + presence.UnRegisterControlEventsToScript(LocalID, ItemID); } } @@ -456,15 +488,23 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance public void RemoveState() { - string savedState = Path.Combine(Path.GetDirectoryName(m_Assembly), - ItemID.ToString() + ".state"); + string savedState = Path.Combine(m_dataPath, ItemID.ToString() + ".state"); + +// m_log.DebugFormat( +// "[SCRIPT INSTANCE]: Deleting state {0} for script {1} (id {2}) in part {3} (id {4}) in object {5} in {6}.", +// savedState, ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name); try { File.Delete(savedState); } - catch(Exception) + catch (Exception e) { + m_log.Warn( + string.Format( + "[SCRIPT INSTANCE]: Could not delete script state {0} for script {1} (id {2}) in part {3} (id {4}) in object {5} in {6}. Exception ", + savedState, ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name), + e); } } @@ -487,8 +527,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance Running = true; TimeStarted = DateTime.Now; - MeasurementPeriodTickStart = Util.EnvironmentTickCount(); - MeasurementPeriodExecutionTime = 0; + + // Note: we don't reset ExecutionTime. The reason is that runaway scripts are stopped and restarted + // automatically, and we *do* want to show that they had high CPU in that case. If we had reset + // ExecutionTime here then runaway scripts, paradoxically, would never show up in the "Top Scripts" dialog. if (EventQueue.Count > 0) { @@ -500,16 +542,20 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance } } - public bool Stop(int timeout) + public bool Stop(int timeout, bool clearEventQueue = false) { -// m_log.DebugFormat( -// "[SCRIPT INSTANCE]: Stopping script {0} {1} in {2} {3} with timeout {4} {5} {6}", -// ScriptName, ItemID, PrimName, ObjectID, timeout, m_InSelfDelete, DateTime.Now.Ticks); + if (DebugLevel >= 1) + m_log.DebugFormat( + "[SCRIPT INSTANCE]: Stopping script {0} {1} in {2} {3} with timeout {4} {5} {6}", + ScriptName, ItemID, PrimName, ObjectID, timeout, m_InSelfDelete, DateTime.Now.Ticks); IScriptWorkItem workItem; lock (EventQueue) { + if (clearEventQueue) + ClearQueue(); + if (!Running) return true; @@ -533,9 +579,36 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance } // Wait for the current event to complete. - if (!m_InSelfDelete && workItem.Wait(new TimeSpan((long)timeout * 100000))) + if (!m_InSelfDelete) { - return true; + if (!m_coopTermination) + { + // If we're not co-operative terminating then try and wait for the event to complete before stopping + if (workItem.Wait(timeout)) + return true; + } + else + { + if (DebugLevel >= 1) + m_log.DebugFormat( + "[SCRIPT INSTANCE]: Co-operatively stopping script {0} {1} in {2} {3}", + ScriptName, ItemID, PrimName, ObjectID); + + // This will terminate the event on next handle check by the script. + m_coopSleepHandle.Set(); + + // For now, we will wait forever since the event should always cleanly terminate once LSL loop + // checking is implemented. May want to allow a shorter timeout option later. + if (workItem.Wait(Timeout.Infinite)) + { + if (DebugLevel >= 1) + m_log.DebugFormat( + "[SCRIPT INSTANCE]: Co-operatively stopped script {0} {1} in {2} {3}", + ScriptName, ItemID, PrimName, ObjectID); + + return true; + } + } } lock (EventQueue) @@ -548,6 +621,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance // If the event still hasn't stopped and we the stop isn't the result of script or object removal, then // forcibly abort the work item (this aborts the underlying thread). + // Co-operative termination should never reach this point. if (!m_InSelfDelete) { m_log.DebugFormat( @@ -570,12 +644,32 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance if (state == State) return; - PostEvent(new EventParams("state_exit", new Object[0], - new DetectParams[0])); - PostEvent(new EventParams("state", new Object[] { state }, - new DetectParams[0])); - PostEvent(new EventParams("state_entry", new Object[0], - new DetectParams[0])); + EventParams lastTimerEv = null; + + lock (EventQueue) + { + // Remove all queued events, remembering the last timer event + while (EventQueue.Count > 0) + { + EventParams tempv = (EventParams)EventQueue.Dequeue(); + if (tempv.EventName == "timer") lastTimerEv = tempv; + } + + // Post events + PostEvent(new EventParams("state_exit", new Object[0], + new DetectParams[0])); + PostEvent(new EventParams("state", new Object[] { state }, + new DetectParams[0])); + PostEvent(new EventParams("state_entry", new Object[0], + new DetectParams[0])); + + // Requeue the timer event after the state changing events + if (lastTimerEv != null) EventQueue.Enqueue(lastTimerEv); + + // This will stop events from being queued and processed + // until the new state is started + m_StateChangeInProgress = true; + } throw new EventAbortException(); } @@ -607,6 +701,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance lock (EventQueue) { + // The only events that persist across state changes are timers + if (m_StateChangeInProgress && data.EventName != "timer") + return; + if (EventQueue.Count >= m_MaxScriptQueue) return; @@ -677,196 +775,222 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance if (Suspended) return 0; - EventParams data = null; + ExecutionTimer.Restart(); + + try + { + return EventProcessorInt(); + } + finally + { + ExecutionTimer.Stop(); + ExecutionTime.AddSample(ExecutionTimer); + Part.ParentGroup.Scene.AddScriptExecutionTime(ExecutionTimer.ElapsedTicks); + } + } + } + + private object EventProcessorInt() + { + EventParams data = null; - lock (EventQueue) + lock (EventQueue) + { + data = (EventParams)EventQueue.Dequeue(); + if (data == null) // Shouldn't happen { - data = (EventParams)EventQueue.Dequeue(); - if (data == null) // Shouldn't happen + if (EventQueue.Count > 0 && Running && !ShuttingDown) { - if (EventQueue.Count > 0 && Running && !ShuttingDown) - { - m_CurrentWorkItem = Engine.QueueEventHandler(this); - } - else - { - m_CurrentWorkItem = null; - } - return 0; + m_CurrentWorkItem = Engine.QueueEventHandler(this); } - - if (data.EventName == "timer") - m_TimerQueued = false; - if (data.EventName == "control") + else { - if (m_ControlEventsInQueue > 0) - m_ControlEventsInQueue--; + m_CurrentWorkItem = null; } - if (data.EventName == "collision") - m_CollisionInQueue = false; + return 0; } - SceneObjectPart part = Engine.World.GetSceneObjectPart(LocalID); + if (data.EventName == "timer") + m_TimerQueued = false; + if (data.EventName == "control") + { + if (m_ControlEventsInQueue > 0) + m_ControlEventsInQueue--; + } + if (data.EventName == "collision") + m_CollisionInQueue = false; + } - if (DebugLevel >= 2) + if (DebugLevel >= 2) + m_log.DebugFormat( + "[SCRIPT INSTANCE]: Processing event {0} for {1}/{2}({3})/{4}({5}) @ {6}/{7}", + data.EventName, + ScriptName, + Part.Name, + Part.LocalId, + Part.ParentGroup.Name, + Part.ParentGroup.UUID, + Part.AbsolutePosition, + Part.ParentGroup.Scene.Name); + + m_DetectParams = data.DetectParams; + + if (data.EventName == "state") // Hardcoded state change + { + State = data.Params[0].ToString(); + + if (DebugLevel >= 1) m_log.DebugFormat( - "[SCRIPT INSTANCE]: Processing event {0} for {1}/{2}({3})/{4}({5}) @ {6}/{7}", - data.EventName, - ScriptName, - part.Name, - part.LocalId, - part.ParentGroup.Name, - part.ParentGroup.UUID, - part.AbsolutePosition, - part.ParentGroup.Scene.Name); - - m_DetectParams = data.DetectParams; - - if (data.EventName == "state") // Hardcoded state change + "[SCRIPT INSTANCE]: Changing state to {0} for {1}/{2}({3})/{4}({5}) @ {6}/{7}", + State, + ScriptName, + Part.Name, + Part.LocalId, + Part.ParentGroup.Name, + Part.ParentGroup.UUID, + Part.AbsolutePosition, + Part.ParentGroup.Scene.Name); + + AsyncCommandManager.StateChange(Engine, + LocalID, ItemID); + // we are effectively in the new state now, so we can resume queueing + // and processing other non-timer events + m_StateChangeInProgress = false; + + Part.SetScriptEvents(ItemID, (int)m_Script.GetStateEventFlags(State)); + } + else + { + if (Engine.World.PipeEventsForScript(LocalID) || + data.EventName == "control") // Don't freeze avies! { - State = data.Params[0].ToString(); + // m_log.DebugFormat("[Script] Delivered event {2} in state {3} to {0}.{1}", + // PrimName, ScriptName, data.EventName, State); - if (DebugLevel >= 1) - m_log.DebugFormat( - "[SCRIPT INSTANCE]: Changing state to {0} for {1}/{2}({3})/{4}({5}) @ {6}/{7}", - State, - ScriptName, - part.Name, - part.LocalId, - part.ParentGroup.Name, - part.ParentGroup.UUID, - part.AbsolutePosition, - part.ParentGroup.Scene.Name); - - AsyncCommandManager.RemoveScript(Engine, - LocalID, ItemID); - - if (part != null) + try { - part.SetScriptEvents(ItemID, - (int)m_Script.GetStateEventFlags(State)); - } - } - else - { - if (Engine.World.PipeEventsForScript(LocalID) || - data.EventName == "control") // Don't freeze avies! - { - // m_log.DebugFormat("[Script] Delivered event {2} in state {3} to {0}.{1}", - // PrimName, ScriptName, data.EventName, State); + m_CurrentEvent = data.EventName; + m_EventStart = DateTime.Now; + m_InEvent = true; try { - m_CurrentEvent = data.EventName; - m_EventStart = DateTime.Now; - m_InEvent = true; - - int start = Util.EnvironmentTickCount(); - - // Reset the measurement period when we reach the end of the current one. - if (start - MeasurementPeriodTickStart > MaxMeasurementPeriod) - MeasurementPeriodTickStart = start; - m_Script.ExecuteEvent(State, data.EventName, data.Params); - - MeasurementPeriodExecutionTime += Util.EnvironmentTickCount() - start; - + } + finally + { m_InEvent = false; m_CurrentEvent = String.Empty; + } - if (m_SaveState) - { - // This will be the very first event we deliver - // (state_entry) in default state - // - SaveState(m_Assembly); + if (m_SaveState) + { + // This will be the very first event we deliver + // (state_entry) in default state + // + SaveState(); - m_SaveState = false; - } + m_SaveState = false; } - catch (Exception e) + } + catch (Exception e) + { + // m_log.DebugFormat( + // "[SCRIPT] Exception in script {0} {1}: {2}{3}", + // ScriptName, ItemID, e.Message, e.StackTrace); + + if ((!(e is TargetInvocationException) + || (!(e.InnerException is SelfDeleteException) + && !(e.InnerException is ScriptDeleteException) + && !(e.InnerException is ScriptCoopStopException))) + && !(e is ThreadAbortException)) { -// m_log.DebugFormat( -// "[SCRIPT] Exception in script {0} {1}: {2}{3}", -// ScriptName, ItemID, e.Message, e.StackTrace); - - m_InEvent = false; - m_CurrentEvent = String.Empty; - - if ((!(e is TargetInvocationException) || (!(e.InnerException is SelfDeleteException) && !(e.InnerException is ScriptDeleteException))) && !(e is ThreadAbortException)) + try { - try - { - // DISPLAY ERROR INWORLD - string text = FormatException(e); - - if (text.Length > 1000) - text = text.Substring(0, 1000); - Engine.World.SimChat(Utils.StringToBytes(text), - ChatTypeEnum.DebugChannel, 2147483647, - part.AbsolutePosition, - part.Name, part.UUID, false); - - - m_log.DebugFormat( - "[SCRIPT INSTANCE]: Runtime error in script {0}, part {1} {2} at {3} in {4}, displayed error {5}, actual exception {6}", - ScriptName, - PrimName, - part.UUID, - part.AbsolutePosition, - part.ParentGroup.Scene.Name, - text.Replace("\n", "\\n"), - e.InnerException); - } - catch (Exception) - { - } - // catch (Exception e2) // LEGIT: User Scripting - // { - // m_log.Error("[SCRIPT]: "+ - // "Error displaying error in-world: " + - // e2.ToString()); - // m_log.Error("[SCRIPT]: " + - // "Errormessage: Error compiling script:\r\n" + - // e.ToString()); - // } + // DISPLAY ERROR INWORLD + string text = FormatException(e); + + if (text.Length > 1000) + text = text.Substring(0, 1000); + Engine.World.SimChat(Utils.StringToBytes(text), + ChatTypeEnum.DebugChannel, 2147483647, + Part.AbsolutePosition, + Part.Name, Part.UUID, false); + + + m_log.Debug(string.Format( + "[SCRIPT INSTANCE]: Runtime error in script {0} (event {1}), part {2} {3} at {4} in {5} ", + ScriptName, + data.EventName, + PrimName, + Part.UUID, + Part.AbsolutePosition, + Part.ParentGroup.Scene.Name), + e); } - else if ((e is TargetInvocationException) && (e.InnerException is SelfDeleteException)) + catch (Exception) { - m_InSelfDelete = true; - if (part != null) - Engine.World.DeleteSceneObject(part.ParentGroup, false); - } - else if ((e is TargetInvocationException) && (e.InnerException is ScriptDeleteException)) - { - m_InSelfDelete = true; - if (part != null) - part.Inventory.RemoveInventoryItem(ItemID); } + // catch (Exception e2) // LEGIT: User Scripting + // { + // m_log.Error("[SCRIPT]: "+ + // "Error displaying error in-world: " + + // e2.ToString()); + // m_log.Error("[SCRIPT]: " + + // "Errormessage: Error compiling script:\r\n" + + // e.ToString()); + // } + } + else if ((e is TargetInvocationException) && (e.InnerException is SelfDeleteException)) + { + m_InSelfDelete = true; + Engine.World.DeleteSceneObject(Part.ParentGroup, false); + } + else if ((e is TargetInvocationException) && (e.InnerException is ScriptDeleteException)) + { + m_InSelfDelete = true; + Part.Inventory.RemoveInventoryItem(ItemID); + } + else if ((e is TargetInvocationException) && (e.InnerException is ScriptCoopStopException)) + { + if (DebugLevel >= 1) + m_log.DebugFormat( + "[SCRIPT INSTANCE]: Script {0}.{1} in event {2}, state {3} stopped co-operatively.", + PrimName, ScriptName, data.EventName, State); } } } + } - // If there are more events and we are currently running and not shutting down, then ask the - // script engine to run the next event. - lock (EventQueue) + // If there are more events and we are currently running and not shutting down, then ask the + // script engine to run the next event. + lock (EventQueue) + { + // Increase processed events counter and prevent wrap; + if (++EventsProcessed == 1000000) + EventsProcessed = 100000; + + if ((EventsProcessed % 100000) == 0 && DebugLevel > 0) { - EventsProcessed++; + m_log.DebugFormat("[SCRIPT INSTANCE]: Script \"{0}\" (Object \"{1}\" {2} @ {3}.{4}, Item ID {5}, Asset {6}) in event {7}: processed {8:n0} script events", + ScriptTask.Name, + Part.ParentGroup.Name, Part.ParentGroup.UUID, Part.ParentGroup.AbsolutePosition, Part.ParentGroup.Scene.Name, + ScriptTask.ItemID, ScriptTask.AssetID, data.EventName, EventsProcessed); + } - if (EventQueue.Count > 0 && Running && !ShuttingDown) - { - m_CurrentWorkItem = Engine.QueueEventHandler(this); - } - else - { - m_CurrentWorkItem = null; - } + if (EventQueue.Count > 0 && Running && !ShuttingDown) + { + m_CurrentWorkItem = Engine.QueueEventHandler(this); } + else + { + m_CurrentWorkItem = null; + } + } - m_DetectParams = null; + m_DetectParams = null; - return 0; - } + return 0; } public int EventTime() @@ -888,19 +1012,21 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance ReleaseControls(); Stop(timeout); - SceneObjectPart part = Engine.World.GetSceneObjectPart(LocalID); - part.Inventory.GetInventoryItem(ItemID).PermsMask = 0; - part.Inventory.GetInventoryItem(ItemID).PermsGranter = UUID.Zero; + Part.Inventory.GetInventoryItem(ItemID).PermsMask = 0; + Part.Inventory.GetInventoryItem(ItemID).PermsGranter = UUID.Zero; AsyncCommandManager.RemoveScript(Engine, LocalID, ItemID); EventQueue.Clear(); m_Script.ResetVars(); + StartParam = 0; State = "default"; - part.SetScriptEvents(ItemID, + Part.SetScriptEvents(ItemID, (int)m_Script.GetStateEventFlags(State)); if (running) Start(); - m_SaveState = true; + + m_SaveState = StatePersistedHere; + PostEvent(new EventParams("state_entry", new Object[0], new DetectParams[0])); } @@ -913,21 +1039,22 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance ReleaseControls(); m_Script.ResetVars(); - SceneObjectPart part = Engine.World.GetSceneObjectPart(LocalID); - part.Inventory.GetInventoryItem(ItemID).PermsMask = 0; - part.Inventory.GetInventoryItem(ItemID).PermsGranter = UUID.Zero; + Part.Inventory.GetInventoryItem(ItemID).PermsMask = 0; + Part.Inventory.GetInventoryItem(ItemID).PermsGranter = UUID.Zero; AsyncCommandManager.RemoveScript(Engine, LocalID, ItemID); EventQueue.Clear(); m_Script.ResetVars(); + string oldState = State; + StartParam = 0; State = "default"; - part.SetScriptEvents(ItemID, + Part.SetScriptEvents(ItemID, (int)m_Script.GetStateEventFlags(State)); - if (m_CurrentEvent != "state_entry") + if (m_CurrentEvent != "state_entry" || oldState != "default") { - m_SaveState = true; + m_SaveState = StatePersistedHere; PostEvent(new EventParams("state_entry", new Object[0], new DetectParams[0])); throw new EventAbortException(); @@ -944,6 +1071,9 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance public void SetVars(Dictionary vars) { +// foreach (KeyValuePair kvp in vars) +// m_log.DebugFormat("[SCRIPT INSTANCE]: Setting var {0}={1}", kvp.Key, kvp.Value); + m_Script.SetVars(vars); } @@ -967,42 +1097,63 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance return m_DetectParams[idx].Key; } - public void SaveState(string assembly) + public void SaveState() { - // If we're currently in an event, just tell it to save upon return - // - if (m_InEvent) - { - m_SaveState = true; + if (!Running && !StayStopped) return; - } + // We cannot call this inside the EventQueue lock since it will currently take AsyncCommandManager.staticLock. + // This may already be held by AsyncCommandManager.DoOneCmdHandlerPass() which in turn can take EventQueue + // lock via ScriptInstance.PostEvent(). PluginData = AsyncCommandManager.GetSerializationData(Engine, ItemID); - string xml = ScriptSerializer.Serialize(this); - - // Compare hash of the state we just just created with the state last written to disk - // If the state is different, update the disk file. - UUID hash = UUID.Parse(Utils.MD5String(xml)); - - if (hash != m_CurrentStateHash) + // We need to lock here to avoid any race with a thread that is removing this script. + lock (EventQueue) { - try + // Check again to avoid a race with a thread in Stop() + if (!Running && !StayStopped) + return; + + // If we're currently in an event, just tell it to save upon return + // + if (m_InEvent) { - FileStream fs = File.Create(Path.Combine(Path.GetDirectoryName(assembly), ItemID.ToString() + ".state")); - Byte[] buf = Util.UTF8NoBomEncoding.GetBytes(xml); - fs.Write(buf, 0, buf.Length); - fs.Close(); + m_SaveState = true; + return; } - catch(Exception) + + // m_log.DebugFormat( + // "[SCRIPT INSTANCE]: Saving state for script {0} (id {1}) in part {2} (id {3}) in object {4} in {5}", + // ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name); + + string xml = ScriptSerializer.Serialize(this); + + // Compare hash of the state we just just created with the state last written to disk + // If the state is different, update the disk file. + UUID hash = UUID.Parse(Utils.MD5String(xml)); + + if (hash != m_CurrentStateHash) { - // m_log.Error("Unable to save xml\n"+e.ToString()); + try + { + using (FileStream fs = File.Create(Path.Combine(m_dataPath, ItemID.ToString() + ".state"))) + { + Byte[] buf = Util.UTF8NoBomEncoding.GetBytes(xml); + fs.Write(buf, 0, buf.Length); + } + } + catch(Exception) + { + // m_log.Error("Unable to save xml\n"+e.ToString()); + } + //if (!File.Exists(Path.Combine(Path.GetDirectoryName(assembly), ItemID.ToString() + ".state"))) + //{ + // throw new Exception("Completed persistence save, but no file was created"); + //} + m_CurrentStateHash = hash; } - //if (!File.Exists(Path.Combine(Path.GetDirectoryName(assembly), ItemID.ToString() + ".state"))) - //{ - // throw new Exception("Completed persistence save, but no file was created"); - //} - m_CurrentStateHash = hash; + + StayStopped = false; } } @@ -1072,7 +1223,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance public string GetAssemblyName() { - return m_Assembly; + return m_assemblyPath; } public string GetXMLState() @@ -1109,4 +1260,23 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance Suspended = false; } } + + /// + /// Xengine event wait handle. + /// + /// + /// This class exists becase XEngineScriptBase gets a reference to this wait handle. We need to make sure that + /// when scripts are running in different AppDomains the lease does not expire. + /// FIXME: Like LSL_Api, etc., this effectively leaks memory since the GC will never collect it. To avoid this, + /// proper remoting sponsorship needs to be implemented across the board. + /// + public class XEngineEventWaitHandle : EventWaitHandle + { + public XEngineEventWaitHandle(bool initialState, EventResetMode mode) : base(initialState, mode) {} + + public override Object InitializeLifetimeService() + { + return null; + } + } } -- cgit v1.1