diff --git a/OpenSim/Framework/ISceneObject.cs b/OpenSim/Framework/ISceneObject.cs index afac9b84f2..754b77b4a2 100644 --- a/OpenSim/Framework/ISceneObject.cs +++ b/OpenSim/Framework/ISceneObject.cs @@ -32,6 +32,8 @@ namespace OpenSim.Framework { public interface ISceneObject { + string Name { get; } + UUID UUID { get; } /// diff --git a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs index a3368fbb1c..ff7a062c9c 100644 --- a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs @@ -255,6 +255,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments public void CopyAttachments(AgentData ad, IScenePresence sp) { +// m_log.DebugFormat("[ATTACHMENTS MODULE]: Copying attachment data into {0} in {1}", sp.Name, m_scene.Name); + if (ad.AttachmentObjects != null && ad.AttachmentObjects.Count > 0) { lock (sp.AttachmentsSyncLock) @@ -265,6 +267,11 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments { ((SceneObjectGroup)so).LocalId = 0; ((SceneObjectGroup)so).RootPart.ClearUpdateSchedule(); + +// m_log.DebugFormat( +// "[ATTACHMENTS MODULE]: Copying script state with {0} bytes for object {1} for {2} in {3}", +// ad.AttachmentObjectStates[i].Length, so.Name, sp.Name, m_scene.Name); + so.SetState(ad.AttachmentObjectStates[i++], m_scene); m_scene.IncomingCreateObject(Vector3.Zero, so); } diff --git a/OpenSim/Region/ScriptEngine/Interfaces/IScriptInstance.cs b/OpenSim/Region/ScriptEngine/Interfaces/IScriptInstance.cs index b9a217b649..1097efb856 100644 --- a/OpenSim/Region/ScriptEngine/Interfaces/IScriptInstance.cs +++ b/OpenSim/Region/ScriptEngine/Interfaces/IScriptInstance.cs @@ -222,7 +222,7 @@ namespace OpenSim.Region.ScriptEngine.Interfaces void SetVars(Dictionary vars); DetectParams GetDetectParams(int idx); UUID GetDetectID(int idx); - void SaveState(string assembly); + void SaveState(); void DestroyScriptInstance(); IScriptApi GetApi(string name); diff --git a/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs b/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs index 637c5562ba..b983be93b6 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs @@ -71,7 +71,8 @@ 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; @@ -244,12 +245,16 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance /// /// /// + /// + /// 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(AppDomain dom, Assembly scriptAssembly, StateSource stateSource) + public bool Load(AppDomain dom, Assembly scriptAssembly, string dataPath, StateSource stateSource) { - //m_Assembly = scriptAssembly.CodeBase; - m_Assembly = scriptAssembly.Location; + m_assemblyPath = scriptAssembly.Location; + m_dataPath = dataPath; m_stateSource = stateSource; try @@ -270,14 +275,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance constructorParams = null; } -// m_log.DebugFormat( -// "[SCRIP -// scriptType.FullName, m_Assembly, Engine.World.Name); - if (dom != System.AppDomain.CurrentDomain) m_Script = (IScript)dom.CreateInstanceAndUnwrap( - Path.GetFileNameWithoutExtension(m_Assembly), + Path.GetFileNameWithoutExtension(m_assemblyPath), scriptType.FullName, false, BindingFlags.Default, @@ -305,7 +306,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance { m_log.ErrorFormat( "[SCRIPT INSTANCE]: Not starting script {0} (id {1}) in part {2} (id {3}) in object {4} in {5}. Error loading assembly {6}. Exception {7}{8}", - ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, m_Assembly, e.Message, e.StackTrace); + ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, scriptAssembly.Location, e.Message, e.StackTrace); return false; } @@ -340,10 +341,14 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance m_SaveState = true; - string savedState = Path.Combine(Path.GetDirectoryName(m_Assembly), ItemID.ToString() + ".state"); + string savedState = Path.Combine(m_dataPath, 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 @@ -385,12 +390,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance m_startedFromSavedState = true; } } - 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); - } +// 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) { @@ -401,11 +406,9 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance } // else // { -// ScenePresence presence = Engine.World.GetScenePresence(part.OwnerID); - -// if (presence != null && (!postOnRez)) -// presence.ControllingClient.SendAgentAlertMessage("Compile successful", false); - +// 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); // } return true; @@ -498,8 +501,11 @@ 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 { @@ -822,8 +828,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance 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_log.DebugFormat("[Script] Delivered event {2} in state {3} to {0}.{1}", +// PrimName, ScriptName, data.EventName, State); try { @@ -849,7 +855,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance // This will be the very first event we deliver // (state_entry) in default state // - SaveState(m_Assembly); + SaveState(); m_SaveState = false; } @@ -1043,7 +1049,7 @@ 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 // @@ -1065,7 +1071,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance { try { - using (FileStream fs = File.Create(Path.Combine(Path.GetDirectoryName(assembly), ItemID.ToString() + ".state"))) + using (FileStream fs = File.Create(Path.Combine(m_dataPath, ItemID.ToString() + ".state"))) { Byte[] buf = Util.UTF8NoBomEncoding.GetBytes(xml); fs.Write(buf, 0, buf.Length); @@ -1149,7 +1155,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance public string GetAssemblyName() { - return m_Assembly; + return m_assemblyPath; } public string GetXMLState() diff --git a/OpenSim/Region/ScriptEngine/XEngine/Tests/XEngineBasicTests.cs b/OpenSim/Region/ScriptEngine/XEngine/Tests/XEngineBasicTests.cs index fe15157af5..878e571617 100644 --- a/OpenSim/Region/ScriptEngine/XEngine/Tests/XEngineBasicTests.cs +++ b/OpenSim/Region/ScriptEngine/XEngine/Tests/XEngineBasicTests.cs @@ -40,10 +40,10 @@ using OpenSim.Tests.Common; namespace OpenSim.Region.ScriptEngine.XEngine.Tests { /// - /// XEngine tests. + /// Basic XEngine tests. /// [TestFixture] - public class XEngineTest : OpenSimTestCase + public class XEngineBasicTests : OpenSimTestCase { private TestScene m_scene; private XEngine m_xEngine; diff --git a/OpenSim/Region/ScriptEngine/XEngine/Tests/XEngineCrossingTests.cs b/OpenSim/Region/ScriptEngine/XEngine/Tests/XEngineCrossingTests.cs new file mode 100644 index 0000000000..587695fc1a --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XEngine/Tests/XEngineCrossingTests.cs @@ -0,0 +1,195 @@ +/* + * 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 OpenSimulator 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.Threading; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ScriptEngine.XEngine.Tests +{ + /// + /// XEngine tests connected with crossing scripts between regions. + /// + [TestFixture] + public class XEngineCrossingTests : OpenSimTestCase + { + [TestFixtureSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + /// + /// Test script state preservation when a script crosses between regions on the same simulator. + /// + [Test] + public void TestScriptCrossOnSameSimulator() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + int sceneObjectIdTail = 0x2; + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + XEngine xEngineA = new XEngine(); + XEngine xEngineB = new XEngine(); + xEngineA.DebugLevel = 1; + xEngineB.DebugLevel = 1; + + IConfigSource configSource = new IniConfigSource(); + + IConfig startupConfig = configSource.AddConfig("Startup"); + startupConfig.Set("DefaultScriptEngine", "XEngine"); + + IConfig xEngineConfig = configSource.AddConfig("XEngine"); + xEngineConfig.Set("Enabled", "true"); + xEngineConfig.Set("StartDelay", "0"); + + // These tests will not run with AppDomainLoading = true, at least on mono. For unknown reasons, the call + // to AssemblyResolver.OnAssemblyResolve fails. + xEngineConfig.Set("AppDomainLoading", "false"); + + IConfig modulesConfig = configSource.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000, configSource); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999, configSource); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, configSource, lscm); + SceneHelpers.SetupSceneModules(sceneA, configSource, etmA, xEngineA); + SceneHelpers.SetupSceneModules(sceneB, configSource, etmB, xEngineB); + sceneA.StartScripts(); + sceneB.StartScripts(); + + SceneObjectGroup soSceneA = SceneHelpers.AddSceneObject(sceneA, 1, userId, "so1-", sceneObjectIdTail); + soSceneA.AbsolutePosition = new Vector3(128, 10, 20); + + // CREATE SCRIPT TODO + InventoryItemBase scriptItemSceneA = new InventoryItemBase(); + // itemTemplate.ID = itemId; + scriptItemSceneA.Name = "script1"; + scriptItemSceneA.Folder = soSceneA.UUID; + scriptItemSceneA.InvType = (int)InventoryType.LSL; + + AutoResetEvent chatEvent = new AutoResetEvent(false); + OSChatMessage messageReceived = null; + sceneA.EventManager.OnChatFromWorld += (s, m) => { messageReceived = m; chatEvent.Set(); }; + + sceneA.RezNewScript(userId, scriptItemSceneA, +@"integer c = 0; + +default +{ + state_entry() + { + llSay(0, ""Script running""); + } + + changed(integer change) + { + llSay(0, ""Changed""); + } + + touch_start(integer n) + { + c = c + 1; + llSay(0, (string)c); + } +}"); + + chatEvent.WaitOne(60000); + + Assert.That(messageReceived, Is.Not.Null, "No chat message received."); + Assert.That(messageReceived.Message, Is.EqualTo("Script running")); + + { + // XXX: Should not be doing this so directly. Should call some variant of EventManager.touch() instead. + DetectParams[] det = new DetectParams[1]; + det[0] = new DetectParams(); + det[0].Key = userId; + det[0].Populate(sceneA); + + EventParams ep = new EventParams("touch_start", new Object[] { new LSL_Types.LSLInteger(1) }, det); + + xEngineA.PostObjectEvent(soSceneA.LocalId, ep); + chatEvent.WaitOne(60000); + + Assert.That(messageReceived.Message, Is.EqualTo("1")); + } + + sceneB.EventManager.OnChatFromWorld += (s, m) => { messageReceived = m; chatEvent.Set(); }; + + // Cross with a negative value + soSceneA.AbsolutePosition = new Vector3(128, -10, 20); + + chatEvent.WaitOne(60000); + Assert.That(messageReceived.Message, Is.EqualTo("Changed")); + + // TEST sending event to moved prim and output + { + SceneObjectGroup soSceneB = sceneB.GetSceneObjectGroup(soSceneA.Name); + TaskInventoryItem scriptItemSceneB = soSceneB.RootPart.Inventory.GetInventoryItem(scriptItemSceneA.Name); + + // XXX: Should not be doing this so directly. Should call some variant of EventManager.touch() instead. + DetectParams[] det = new DetectParams[1]; + det[0] = new DetectParams(); + det[0].Key = userId; + det[0].Populate(sceneB); + + EventParams ep = new EventParams("touch_start", new Object[] { new LSL_Types.LSLInteger(1) }, det); + + xEngineB.PostObjectEvent(soSceneB.LocalId, ep); + chatEvent.WaitOne(60000); + + Assert.That(messageReceived.Message, Is.EqualTo("2")); + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs b/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs index ccd5d990fd..71ed9891c4 100644 --- a/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs +++ b/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs @@ -716,22 +716,17 @@ namespace OpenSim.Region.ScriptEngine.XEngine { // Force a final state save // - if (m_Assemblies.ContainsKey(instance.AssetID)) + try { - string assembly = m_Assemblies[instance.AssetID]; - - try - { - instance.SaveState(assembly); - } - catch (Exception e) - { - m_log.Error( - string.Format( - "[XEngine]: Failed final state save for script {0}.{1}, item UUID {2}, prim UUID {3} in {4}. Exception ", - instance.PrimName, instance.ScriptName, instance.ItemID, instance.ObjectID, World.Name) - , e); - } + instance.SaveState(); + } + catch (Exception e) + { + m_log.Error( + string.Format( + "[XEngine]: Failed final state save for script {0}.{1}, item UUID {2}, prim UUID {3} in {4}. Exception ", + instance.PrimName, instance.ScriptName, instance.ItemID, instance.ObjectID, World.Name) + , e); } // Clear the event queue and abort the instance thread @@ -840,18 +835,9 @@ namespace OpenSim.Region.ScriptEngine.XEngine foreach (IScriptInstance i in instances) { - string assembly = String.Empty; - - lock (m_Scripts) - { - if (!m_Assemblies.ContainsKey(i.AssetID)) - continue; - assembly = m_Assemblies[i.AssetID]; - } - try { - i.SaveState(assembly); + i.SaveState(); } catch (Exception e) { @@ -1180,7 +1166,11 @@ namespace OpenSim.Region.ScriptEngine.XEngine lock (m_AddingAssemblies) { m_Compiler.PerformScriptCompile(script, assetID.ToString(), item.OwnerID, out assemblyPath, out linemap); - + +// m_log.DebugFormat( +// "[XENGINE]: Found assembly path {0} onrez {1} in {2}", +// assemblyPath, item.ItemID, World.Name); + if (!m_AddingAssemblies.ContainsKey(assemblyPath)) { m_AddingAssemblies[assemblyPath] = 1; } else { @@ -1373,7 +1363,9 @@ namespace OpenSim.Region.ScriptEngine.XEngine startParam, postOnRez, m_MaxScriptQueue); - if (!instance.Load(m_AppDomains[appDomain], scriptAssembly, stateSource)) + if (!instance.Load( + m_AppDomains[appDomain], scriptAssembly, + Path.Combine(ScriptEnginePath, World.RegionInfo.RegionID.ToString()), stateSource)) return false; // if (DebugLevel >= 1) @@ -1604,7 +1596,7 @@ namespace OpenSim.Region.ScriptEngine.XEngine IScriptInstance instance = (ScriptInstance) parms; - //m_log.DebugFormat("[XEngine]: Processing event for {0}", instance); +// m_log.DebugFormat("[XEngine]: Processing event for {0}", instance); return instance.EventProcessor(); } @@ -2200,6 +2192,9 @@ namespace OpenSim.Region.ScriptEngine.XEngine m_log.ErrorFormat("[XEngine]: Error whilst writing state file {0}, {1}", statepath, ex.Message); } +// m_log.DebugFormat( +// "[XEngine]: Wrote state for script item with ID {0} at {1} in {2}", itemID, statepath, m_Scene.Name); + return true; }