diff options
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs | 100 | ||||
-rw-r--r-- | OpenSim/Region/ScriptEngine/Shared/Instance/Tests/CoopTerminationTests.cs | 157 |
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 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Threading; | ||
31 | using Nini.Config; | ||
32 | using NUnit.Framework; | ||
33 | using OpenMetaverse; | ||
34 | using OpenSim.Framework; | ||
35 | using OpenSim.Region.CoreModules.Scripting.WorldComm; | ||
36 | using OpenSim.Region.Framework.Scenes; | ||
37 | using OpenSim.Region.Framework.Interfaces; | ||
38 | using OpenSim.Region.ScriptEngine.XEngine; | ||
39 | using OpenSim.Tests.Common; | ||
40 | using OpenSim.Tests.Common.Mock; | ||
41 | |||
42 | namespace 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 | ||