aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ScriptEngine/Shared/Instance
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs100
-rw-r--r--OpenSim/Region/ScriptEngine/Shared/Instance/Tests/CoopTerminationTests.cs157
2 files changed, 233 insertions, 24 deletions
diff --git a/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs b/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs
index f172216..75aea2b 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs
@@ -157,9 +157,6 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
157 157
158 public UUID AppDomain { get; set; } 158 public UUID AppDomain { get; set; }
159 159
160 /// <summary>
161 /// Scene part in which this script instance is contained.
162 /// </summary>
163 public SceneObjectPart Part { get; private set; } 160 public SceneObjectPart Part { get; private set; }
164 161
165 public string PrimName { get; private set; } 162 public string PrimName { get; private set; }
@@ -203,49 +200,68 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
203 200
204 public static readonly long MaxMeasurementPeriod = 30 * TimeSpan.TicksPerMinute; 201 public static readonly long MaxMeasurementPeriod = 30 * TimeSpan.TicksPerMinute;
205 202
203 private bool m_coopTermination;
204
205 private EventWaitHandle m_coopSleepHandle;
206
206 public void ClearQueue() 207 public void ClearQueue()
207 { 208 {
208 m_TimerQueued = false; 209 m_TimerQueued = false;
209 EventQueue.Clear(); 210 EventQueue.Clear();
210 } 211 }
211 212
212 public ScriptInstance(IScriptEngine engine, SceneObjectPart part, 213 public ScriptInstance(
213 UUID itemID, UUID assetID, string assembly, 214 IScriptEngine engine, SceneObjectPart part, TaskInventoryItem item,
214 AppDomain dom, string primName, string scriptName, 215 int startParam, bool postOnRez,
215 int startParam, bool postOnRez, StateSource stateSource, 216 int maxScriptQueue)
216 int maxScriptQueue)
217 { 217 {
218 State = "default"; 218 State = "default";
219 EventQueue = new Queue(32); 219 EventQueue = new Queue(32);
220 220
221 Engine = engine; 221 Engine = engine;
222 Part = part; 222 Part = part;
223 ItemID = itemID; 223 ScriptTask = item;
224 AssetID = assetID; 224
225 PrimName = primName; 225 // This is currently only here to allow regression tests to get away without specifying any inventory
226 ScriptName = scriptName; 226 // item when they are testing script logic that doesn't require an item.
227 m_Assembly = assembly; 227 if (ScriptTask != null)
228 {
229 ScriptName = ScriptTask.Name;
230 ItemID = ScriptTask.ItemID;
231 AssetID = ScriptTask.AssetID;
232 }
233
234 PrimName = part.ParentGroup.Name;
228 StartParam = startParam; 235 StartParam = startParam;
229 m_MaxScriptQueue = maxScriptQueue; 236 m_MaxScriptQueue = maxScriptQueue;
230 m_stateSource = stateSource;
231 m_postOnRez = postOnRez; 237 m_postOnRez = postOnRez;
232 m_AttachedAvatar = Part.ParentGroup.AttachedAvatar; 238 m_AttachedAvatar = Part.ParentGroup.AttachedAvatar;
233 m_RegionID = Part.ParentGroup.Scene.RegionInfo.RegionID; 239 m_RegionID = Part.ParentGroup.Scene.RegionInfo.RegionID;
234 240
235 lock (Part.TaskInventory) 241 if (Engine.Config.GetString("ScriptStopStrategy", "abort") == "co-op")
236 { 242 {
237 if (Part.TaskInventory.ContainsKey(ItemID)) 243 m_coopTermination = true;
238 { 244 m_coopSleepHandle = new AutoResetEvent(false);
239 ScriptTask = Part.TaskInventory[ItemID];
240 }
241 } 245 }
246 }
247
248 /// <summary>
249 /// Load the script from an assembly into an AppDomain.
250 /// </summary>
251 /// <param name='dom'></param>
252 /// <param name='assembly'></param>
253 /// <param name='stateSource'></param>
254 public void Load(AppDomain dom, string assembly, StateSource stateSource)
255 {
256 m_Assembly = assembly;
257 m_stateSource = stateSource;
242 258
243 ApiManager am = new ApiManager(); 259 ApiManager am = new ApiManager();
244 260
245 foreach (string api in am.GetApis()) 261 foreach (string api in am.GetApis())
246 { 262 {
247 m_Apis[api] = am.CreateApi(api); 263 m_Apis[api] = am.CreateApi(api);
248 m_Apis[api].Initialize(engine, part, ScriptTask); 264 m_Apis[api].Initialize(Engine, Part, ScriptTask, m_coopSleepHandle);
249 } 265 }
250 266
251 try 267 try
@@ -279,7 +295,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
279 295
280// // m_log.Debug("[Script] Script instance created"); 296// // m_log.Debug("[Script] Script instance created");
281 297
282 part.SetScriptEvents(ItemID, 298 Part.SetScriptEvents(ItemID,
283 (int)m_Script.GetStateEventFlags(State)); 299 (int)m_Script.GetStateEventFlags(State));
284 } 300 }
285 catch (Exception e) 301 catch (Exception e)
@@ -526,9 +542,34 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
526 } 542 }
527 543
528 // Wait for the current event to complete. 544 // Wait for the current event to complete.
529 if (!m_InSelfDelete && workItem.Wait(new TimeSpan((long)timeout * 100000))) 545 if (!m_InSelfDelete)
530 { 546 {
531 return true; 547 if (!m_coopTermination)
548 {
549 // If we're not co-operative terminating then try and wait for the event to complete before stopping
550 if (workItem.Wait(new TimeSpan((long)timeout * 100000)))
551 return true;
552 }
553 else
554 {
555 m_log.DebugFormat(
556 "[SCRIPT INSTANCE]: Co-operatively stopping script {0} {1} in {2} {3}",
557 ScriptName, ItemID, PrimName, ObjectID);
558
559 // This will terminate the event on next handle check by the script.
560 m_coopSleepHandle.Set();
561
562 // For now, we will wait forever since the event should always cleanly terminate once LSL loop
563 // checking is implemented. May want to allow a shorter timeout option later.
564 if (workItem.Wait(TimeSpan.MaxValue))
565 {
566 m_log.DebugFormat(
567 "[SCRIPT INSTANCE]: Co-operatively stopped script {0} {1} in {2} {3}",
568 ScriptName, ItemID, PrimName, ObjectID);
569
570 return true;
571 }
572 }
532 } 573 }
533 574
534 lock (EventQueue) 575 lock (EventQueue)
@@ -541,6 +582,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
541 582
542 // If the event still hasn't stopped and we the stop isn't the result of script or object removal, then 583 // If the event still hasn't stopped and we the stop isn't the result of script or object removal, then
543 // forcibly abort the work item (this aborts the underlying thread). 584 // forcibly abort the work item (this aborts the underlying thread).
585 // Co-operative termination should never reach this point.
544 if (!m_InSelfDelete) 586 if (!m_InSelfDelete)
545 { 587 {
546 m_log.DebugFormat( 588 m_log.DebugFormat(
@@ -780,7 +822,11 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
780 m_InEvent = false; 822 m_InEvent = false;
781 m_CurrentEvent = String.Empty; 823 m_CurrentEvent = String.Empty;
782 824
783 if ((!(e is TargetInvocationException) || (!(e.InnerException is SelfDeleteException) && !(e.InnerException is ScriptDeleteException))) && !(e is ThreadAbortException)) 825 if ((!(e is TargetInvocationException)
826 || (!(e.InnerException is SelfDeleteException)
827 && !(e.InnerException is ScriptDeleteException)
828 && !(e.InnerException is ScriptCoopStopException)))
829 && !(e is ThreadAbortException))
784 { 830 {
785 try 831 try
786 { 832 {
@@ -828,6 +874,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
828 m_InSelfDelete = true; 874 m_InSelfDelete = true;
829 Part.Inventory.RemoveInventoryItem(ItemID); 875 Part.Inventory.RemoveInventoryItem(ItemID);
830 } 876 }
877 else if ((e is TargetInvocationException) && (e.InnerException is ScriptCoopStopException))
878 {
879 m_log.DebugFormat(
880 "[SCRIPT INSTANCE]: Script {0}.{1} in event {2}, state {3} stopped co-operatively.",
881 PrimName, ScriptName, data.EventName, State);
882 }
831 } 883 }
832 } 884 }
833 } 885 }
diff --git a/OpenSim/Region/ScriptEngine/Shared/Instance/Tests/CoopTerminationTests.cs b/OpenSim/Region/ScriptEngine/Shared/Instance/Tests/CoopTerminationTests.cs
new file mode 100644
index 0000000..8c3e9e0
--- /dev/null
+++ b/OpenSim/Region/ScriptEngine/Shared/Instance/Tests/CoopTerminationTests.cs
@@ -0,0 +1,157 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.Threading;
31using Nini.Config;
32using NUnit.Framework;
33using OpenMetaverse;
34using OpenSim.Framework;
35using OpenSim.Region.CoreModules.Scripting.WorldComm;
36using OpenSim.Region.Framework.Scenes;
37using OpenSim.Region.Framework.Interfaces;
38using OpenSim.Region.ScriptEngine.XEngine;
39using OpenSim.Tests.Common;
40using OpenSim.Tests.Common.Mock;
41
42namespace OpenSim.Region.ScriptEngine.Shared.Instance.Tests
43{
44 /// <summary>
45 /// Test that co-operative script thread termination is working correctly.
46 /// </summary>
47 [TestFixture]
48 public class CoopTerminationTests : OpenSimTestCase
49 {
50 private TestScene m_scene;
51 private OpenSim.Region.ScriptEngine.XEngine.XEngine m_xEngine;
52
53 private AutoResetEvent m_chatEvent = new AutoResetEvent(false);
54 private AutoResetEvent m_stoppedEvent = new AutoResetEvent(false);
55
56 private OSChatMessage m_osChatMessageReceived;
57
58 [TestFixtureSetUp]
59 public void Init()
60 {
61 //AppDomain.CurrentDomain.SetData("APPBASE", Environment.CurrentDirectory + "/bin");
62// Console.WriteLine(AppDomain.CurrentDomain.BaseDirectory);
63 m_xEngine = new OpenSim.Region.ScriptEngine.XEngine.XEngine();
64
65 IniConfigSource configSource = new IniConfigSource();
66
67 IConfig startupConfig = configSource.AddConfig("Startup");
68 startupConfig.Set("DefaultScriptEngine", "XEngine");
69
70 IConfig xEngineConfig = configSource.AddConfig("XEngine");
71 xEngineConfig.Set("Enabled", "true");
72 xEngineConfig.Set("StartDelay", "0");
73
74 // These tests will not run with AppDomainLoading = true, at least on mono. For unknown reasons, the call
75 // to AssemblyResolver.OnAssemblyResolve fails.
76 xEngineConfig.Set("AppDomainLoading", "false");
77
78 xEngineConfig.Set("ScriptStopStrategy", "co-op");
79
80 m_scene = new SceneHelpers().SetupScene("My Test", UUID.Random(), 1000, 1000, configSource);
81 SceneHelpers.SetupSceneModules(m_scene, configSource, m_xEngine);
82 m_scene.StartScripts();
83 }
84
85 /// <summary>
86 /// Test co-operative termination on derez of an object containing a script with a long-running event.
87 /// </summary>
88 /// <remarks>
89 /// TODO: Actually compiling the script is incidental to this test. Really want a way to compile test scripts
90 /// within the build itself.
91 /// </remarks>
92 [Test]
93 public void TestStopOnLongSleep()
94 {
95 TestHelpers.InMethod();
96// TestHelpers.EnableLogging();
97
98 UUID userId = TestHelpers.ParseTail(0x1);
99// UUID objectId = TestHelpers.ParseTail(0x100);
100// UUID itemId = TestHelpers.ParseTail(0x3);
101 string itemName = "TestStopOnObjectDerezLongSleep() Item";
102
103 SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, userId, "TestStopOnObjectDerezLongSleep", 0x100);
104 m_scene.AddNewSceneObject(so, true);
105
106 InventoryItemBase itemTemplate = new InventoryItemBase();
107// itemTemplate.ID = itemId;
108 itemTemplate.Name = itemName;
109 itemTemplate.Folder = so.UUID;
110 itemTemplate.InvType = (int)InventoryType.LSL;
111
112 m_scene.EventManager.OnChatFromWorld += OnChatFromWorld;
113
114 SceneObjectPart partWhereRezzed = m_scene.RezNewScript(userId, itemTemplate,
115@"default
116{
117 state_entry()
118 {
119 llSay(0, ""Thin Lizzy"");
120 llSleep(60);
121 }
122}");
123
124 TaskInventoryItem rezzedItem = partWhereRezzed.Inventory.GetInventoryItem(itemName);
125
126 // Wait for the script to start the event before we try stopping it.
127 m_chatEvent.WaitOne(60000);
128
129 Console.WriteLine("Script started with message [{0}]", m_osChatMessageReceived.Message);
130
131 // FIXME: This is a very poor way of trying to avoid a low-probability race condition where the script
132 // executes llSay() but has not started the sleep before we try to stop it.
133 Thread.Sleep(1000);
134
135 // We need a way of carrying on if StopScript() fail, since it won't return if the script isn't actually
136 // stopped. This kind of multi-threading is far from ideal in a regression test.
137 new Thread(() => { m_xEngine.StopScript(rezzedItem.ItemID); m_stoppedEvent.Set(); }).Start();
138
139 if (!m_stoppedEvent.WaitOne(30000))
140 Assert.Fail("Script did not co-operatively stop.");
141
142 bool running;
143 TaskInventoryItem scriptItem = partWhereRezzed.Inventory.GetInventoryItem(itemName);
144 Assert.That(
145 SceneObjectPartInventory.TryGetScriptInstanceRunning(m_scene, scriptItem, out running), Is.True);
146 Assert.That(running, Is.False);
147 }
148
149 private void OnChatFromWorld(object sender, OSChatMessage oscm)
150 {
151// Console.WriteLine("Got chat [{0}]", oscm.Message);
152
153 m_osChatMessageReceived = oscm;
154 m_chatEvent.Set();
155 }
156 }
157} \ No newline at end of file