aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ScriptEngine
diff options
context:
space:
mode:
authorJustin Clark-Casey (justincc)2013-01-16 00:12:40 +0000
committerJustin Clark-Casey (justincc)2013-01-16 00:12:40 +0000
commit1b5c41c14ad11325be249ea1cce3c65d4d6a89be (patch)
tree987e140c9402c48cc8daf59d2b8af165646cc93a /OpenSim/Region/ScriptEngine
parentInstead of passing separate engine, part and item components to script APIs, ... (diff)
downloadopensim-SC_OLD-1b5c41c14ad11325be249ea1cce3c65d4d6a89be.zip
opensim-SC_OLD-1b5c41c14ad11325be249ea1cce3c65d4d6a89be.tar.gz
opensim-SC_OLD-1b5c41c14ad11325be249ea1cce3c65d4d6a89be.tar.bz2
opensim-SC_OLD-1b5c41c14ad11325be249ea1cce3c65d4d6a89be.tar.xz
Implement co-operative script termination if termination comes during a script wait event (llSleep(), etc.)
This makes use of EventWaitHandles since various web references indicate that Thread.Interrupt() can also cause runtime instability. If co-op termination is enabled, then termination sets the wait handle instead of waiting for a timeout before possibly aborting the thread. This allows the script to cleanly terminate if it's in a llSleep/LL function delay or the next time it enters such a wait without any timeout period. Co-op termination is not yet testable since checking for termination request within loops that never trigger a wait is not yet implemented.
Diffstat (limited to 'OpenSim/Region/ScriptEngine')
-rw-r--r--OpenSim/Region/ScriptEngine/Interfaces/IScriptInstance.cs13
-rw-r--r--OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs31
-rw-r--r--OpenSim/Region/ScriptEngine/Shared/Helpers.cs18
-rw-r--r--OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs52
-rw-r--r--OpenSim/Region/ScriptEngine/Shared/Instance/Tests/CoopTerminationTests.cs157
-rw-r--r--OpenSim/Region/ScriptEngine/XEngine/XEngine.cs5
6 files changed, 266 insertions, 10 deletions
diff --git a/OpenSim/Region/ScriptEngine/Interfaces/IScriptInstance.cs b/OpenSim/Region/ScriptEngine/Interfaces/IScriptInstance.cs
index 9de2d72..38fff52 100644
--- a/OpenSim/Region/ScriptEngine/Interfaces/IScriptInstance.cs
+++ b/OpenSim/Region/ScriptEngine/Interfaces/IScriptInstance.cs
@@ -28,6 +28,7 @@
28using System; 28using System;
29using System.Collections; 29using System.Collections;
30using System.Collections.Generic; 30using System.Collections.Generic;
31using System.Threading;
31using OpenMetaverse; 32using OpenMetaverse;
32using log4net; 33using log4net;
33using OpenSim.Framework; 34using OpenSim.Framework;
@@ -181,6 +182,18 @@ namespace OpenSim.Region.ScriptEngine.Interfaces
181 void Resume(); 182 void Resume();
182 183
183 /// <summary> 184 /// <summary>
185 /// If true then scripts should look to terminate their threads in co-operation with the script engine rather
186 /// than through Thread.Abort()
187 /// </summary>
188 bool CoopTermination { get; }
189
190 /// <summary>
191 /// Used for script sleeps when we are using co-operative script termination.
192 /// </summary>
193 /// <remarks>null if CoopTermination is not active</remarks>
194 EventWaitHandle CoopSleepHandle { get; }
195
196 /// <summary>
184 /// Process the next event queued for this script instance. 197 /// Process the next event queued for this script instance.
185 /// </summary> 198 /// </summary>
186 /// <returns></returns> 199 /// <returns></returns>
diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
index 44072c6..b992efa 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
@@ -83,6 +83,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
83 public class LSL_Api : MarshalByRefObject, ILSL_Api, IScriptApi 83 public class LSL_Api : MarshalByRefObject, ILSL_Api, IScriptApi
84 { 84 {
85 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 85 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
86
87 /// <summary>
88 /// Instance of this script.
89 /// </summary>
90 protected IScriptInstance m_scriptInstance;
91
86 protected IScriptEngine m_ScriptEngine; 92 protected IScriptEngine m_ScriptEngine;
87 protected SceneObjectPart m_host; 93 protected SceneObjectPart m_host;
88 94
@@ -112,11 +118,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
112 118
113 public void Initialize(IScriptInstance scriptInstance) 119 public void Initialize(IScriptInstance scriptInstance)
114 { 120 {
115 m_ScriptEngine = scriptInstance.Engine; 121 m_scriptInstance = scriptInstance;
116 m_host = scriptInstance.Part; 122 m_ScriptEngine = m_scriptInstance.Engine;
117 m_item = scriptInstance.ScriptTask; 123 m_host = m_scriptInstance.Part;
124 m_item = m_scriptInstance.ScriptTask;
118 125
119 LoadLimits(); // read script limits from config. 126 LoadConfig();
120 127
121 m_TransferModule = 128 m_TransferModule =
122 m_ScriptEngine.World.RequestModuleInterface<IMessageTransferModule>(); 129 m_ScriptEngine.World.RequestModuleInterface<IMessageTransferModule>();
@@ -129,7 +136,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
129 /// <summary> 136 /// <summary>
130 /// Load configuration items that affect script, object and run-time behavior. */ 137 /// Load configuration items that affect script, object and run-time behavior. */
131 /// </summary> 138 /// </summary>
132 private void LoadLimits() 139 private void LoadConfig()
133 { 140 {
134 m_ScriptDelayFactor = 141 m_ScriptDelayFactor =
135 m_ScriptEngine.Config.GetFloat("ScriptDelayFactor", 1.0f); 142 m_ScriptEngine.Config.GetFloat("ScriptDelayFactor", 1.0f);
@@ -175,7 +182,16 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
175 delay = (int)((float)delay * m_ScriptDelayFactor); 182 delay = (int)((float)delay * m_ScriptDelayFactor);
176 if (delay == 0) 183 if (delay == 0)
177 return; 184 return;
178 System.Threading.Thread.Sleep(delay); 185
186 Sleep(delay);
187 }
188
189 protected virtual void Sleep(int delay)
190 {
191 if (!m_scriptInstance.CoopTermination)
192 System.Threading.Thread.Sleep(delay);
193 else if (m_scriptInstance.CoopSleepHandle.WaitOne(delay))
194 throw new ScriptCoopStopException();
179 } 195 }
180 196
181 public Scene World 197 public Scene World
@@ -2914,7 +2930,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
2914 { 2930 {
2915// m_log.Info("llSleep snoozing " + sec + "s."); 2931// m_log.Info("llSleep snoozing " + sec + "s.");
2916 m_host.AddScriptLPS(1); 2932 m_host.AddScriptLPS(1);
2917 Thread.Sleep((int)(sec * 1000)); 2933
2934 Sleep((int)(sec * 1000));
2918 } 2935 }
2919 2936
2920 public LSL_Float llGetMass() 2937 public LSL_Float llGetMass()
diff --git a/OpenSim/Region/ScriptEngine/Shared/Helpers.cs b/OpenSim/Region/ScriptEngine/Shared/Helpers.cs
index 5a58f73..e02d35e 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Helpers.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Helpers.cs
@@ -81,6 +81,24 @@ namespace OpenSim.Region.ScriptEngine.Shared
81 } 81 }
82 } 82 }
83 83
84 /// <summary>
85 /// Used to signal when the script is stopping in co-operation with the script engine
86 /// (instead of through Thread.Abort()).
87 /// </summary>
88 [Serializable]
89 public class ScriptCoopStopException : Exception
90 {
91 public ScriptCoopStopException()
92 {
93 }
94
95 protected ScriptCoopStopException(
96 SerializationInfo info,
97 StreamingContext context)
98 {
99 }
100 }
101
84 public class DetectParams 102 public class DetectParams
85 { 103 {
86 public const int AGENT = 1; 104 public const int AGENT = 1;
diff --git a/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs b/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs
index a2ff51b..00048a1 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs
@@ -200,6 +200,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
200 200
201 public static readonly long MaxMeasurementPeriod = 30 * TimeSpan.TicksPerMinute; 201 public static readonly long MaxMeasurementPeriod = 30 * TimeSpan.TicksPerMinute;
202 202
203 public bool CoopTermination { get; private set; }
204
205 public EventWaitHandle CoopSleepHandle { get; private set; }
206
203 public void ClearQueue() 207 public void ClearQueue()
204 { 208 {
205 m_TimerQueued = false; 209 m_TimerQueued = false;
@@ -233,6 +237,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
233 m_postOnRez = postOnRez; 237 m_postOnRez = postOnRez;
234 m_AttachedAvatar = Part.ParentGroup.AttachedAvatar; 238 m_AttachedAvatar = Part.ParentGroup.AttachedAvatar;
235 m_RegionID = Part.ParentGroup.Scene.RegionInfo.RegionID; 239 m_RegionID = Part.ParentGroup.Scene.RegionInfo.RegionID;
240
241 if (Engine.Config.GetString("ScriptStopStrategy", "abort") == "co-op")
242 {
243 CoopTermination = true;
244 CoopSleepHandle = new AutoResetEvent(false);
245 }
236 } 246 }
237 247
238 /// <summary> 248 /// <summary>
@@ -532,9 +542,34 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
532 } 542 }
533 543
534 // Wait for the current event to complete. 544 // Wait for the current event to complete.
535 if (!m_InSelfDelete && workItem.Wait(new TimeSpan((long)timeout * 100000))) 545 if (!m_InSelfDelete)
536 { 546 {
537 return true; 547 if (!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 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 }
538 } 573 }
539 574
540 lock (EventQueue) 575 lock (EventQueue)
@@ -547,6 +582,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
547 582
548 // 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
549 // 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.
550 if (!m_InSelfDelete) 586 if (!m_InSelfDelete)
551 { 587 {
552 m_log.DebugFormat( 588 m_log.DebugFormat(
@@ -786,7 +822,11 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
786 m_InEvent = false; 822 m_InEvent = false;
787 m_CurrentEvent = String.Empty; 823 m_CurrentEvent = String.Empty;
788 824
789 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))
790 { 830 {
791 try 831 try
792 { 832 {
@@ -834,6 +874,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance
834 m_InSelfDelete = true; 874 m_InSelfDelete = true;
835 Part.Inventory.RemoveInventoryItem(ItemID); 875 Part.Inventory.RemoveInventoryItem(ItemID);
836 } 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 }
837 } 883 }
838 } 884 }
839 } 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..f3a6cc9
--- /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
diff --git a/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs b/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs
index 186ae04..a17a018 100644
--- a/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs
+++ b/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs
@@ -1716,9 +1716,14 @@ namespace OpenSim.Region.ScriptEngine.XEngine
1716 IScriptInstance instance = GetInstance(itemID); 1716 IScriptInstance instance = GetInstance(itemID);
1717 1717
1718 if (instance != null) 1718 if (instance != null)
1719 {
1719 instance.Stop(m_WaitForEventCompletionOnScriptStop); 1720 instance.Stop(m_WaitForEventCompletionOnScriptStop);
1721 }
1720 else 1722 else
1723 {
1724// m_log.DebugFormat("[XENGINE]: Could not find script with ID {0} to stop in {1}", itemID, World.Name);
1721 m_runFlags.AddOrUpdate(itemID, false, 240); 1725 m_runFlags.AddOrUpdate(itemID, false, 240);
1726 }
1722 } 1727 }
1723 1728
1724 public DetectParams GetDetectParams(UUID itemID, int idx) 1729 public DetectParams GetDetectParams(UUID itemID, int idx)