aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ScriptEngine
diff options
context:
space:
mode:
authorTedd Hansen2008-02-01 19:07:05 +0000
committerTedd Hansen2008-02-01 19:07:05 +0000
commit5d6e89eaf924e741be95248d3ce5bc4ddbd5de3c (patch)
tree9861db7e5006d2c7b259fa2c8c266e066a7286b2 /OpenSim/Region/ScriptEngine
parentFixed errors being thrown by invalid PSYS_SRC_TARGET_KEY's in llParticleSyste... (diff)
downloadopensim-SC-5d6e89eaf924e741be95248d3ce5bc4ddbd5de3c.zip
opensim-SC-5d6e89eaf924e741be95248d3ce5bc4ddbd5de3c.tar.gz
opensim-SC-5d6e89eaf924e741be95248d3ce5bc4ddbd5de3c.tar.bz2
opensim-SC-5d6e89eaf924e741be95248d3ce5bc4ddbd5de3c.tar.xz
Highly experimental
A separate thread is used to enforce max function (event) execution time for scripts.
Diffstat (limited to 'OpenSim/Region/ScriptEngine')
-rw-r--r--OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs248
-rw-r--r--OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueThreadClass.cs201
2 files changed, 300 insertions, 149 deletions
diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs
index 949d5b6..70db724 100644
--- a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs
+++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs
@@ -69,29 +69,31 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
69 /// <summary> 69 /// <summary>
70 /// List of threads processing event queue 70 /// List of threads processing event queue
71 /// </summary> 71 /// </summary>
72 private List<Thread> eventQueueThreads = new List<Thread>(); 72 private List<EventQueueThreadClass> eventQueueThreads = new List<EventQueueThreadClass>();
73 private object eventQueueThreadsLock = new object();
73 74
74 private object queueLock = new object(); // Mutex lock object 75 public object queueLock = new object(); // Mutex lock object
75 76
76 /// <summary> 77 /// <summary>
77 /// How many ms to sleep if queue is empty 78 /// How many threads to process queue with
78 /// </summary> 79 /// </summary>
79 private int nothingToDoSleepms = 50; 80 private int numberOfThreads = 2;
80 81
81 /// <summary> 82 /// <summary>
82 /// How many threads to process queue with 83 /// Maximum time one function can use for execution before we perform a thread kill
83 /// </summary> 84 /// </summary>
84 private int numberOfThreads = 2; 85 private int maxFunctionExecutionTimems = 50;
86 private bool EnforceMaxExecutionTime = true;
85 87
86 /// <summary> 88 /// <summary>
87 /// Queue containing events waiting to be executed 89 /// Queue containing events waiting to be executed
88 /// </summary> 90 /// </summary>
89 private Queue<QueueItemStruct> eventQueue = new Queue<QueueItemStruct>(); 91 public Queue<QueueItemStruct> eventQueue = new Queue<QueueItemStruct>();
90 92
91 /// <summary> 93 /// <summary>
92 /// Queue item structure 94 /// Queue item structure
93 /// </summary> 95 /// </summary>
94 private struct QueueItemStruct 96 public struct QueueItemStruct
95 { 97 {
96 public uint localID; 98 public uint localID;
97 public LLUUID itemID; 99 public LLUUID itemID;
@@ -118,7 +120,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
118 public LSL_Types.Vector3[] _Vector3; 120 public LSL_Types.Vector3[] _Vector3;
119 public bool[] _bool; 121 public bool[] _bool;
120 public int[] _int; 122 public int[] _int;
121 public string [] _string; 123 public string[] _string;
122 } 124 }
123 125
124 /// <summary> 126 /// <summary>
@@ -128,177 +130,72 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
128 130
129 private object tryLockLock = new object(); // Mutex lock object 131 private object tryLockLock = new object(); // Mutex lock object
130 132
131 private ScriptEngine m_ScriptEngine; 133 public ScriptEngine m_ScriptEngine;
134
135 public Thread ExecutionTimeoutEnforcingThread;
132 136
133 public EventQueueManager(ScriptEngine _ScriptEngine) 137 public EventQueueManager(ScriptEngine _ScriptEngine)
134 { 138 {
135 m_ScriptEngine = _ScriptEngine; 139 m_ScriptEngine = _ScriptEngine;
136 140
141 // Start function max exec time enforcement thread
142 if (EnforceMaxExecutionTime)
143 {
144 ExecutionTimeoutEnforcingThread = new Thread(ExecutionTimeoutEnforcingLoop);
145 ExecutionTimeoutEnforcingThread.Name = "ExecutionTimeoutEnforcingThread";
146 ExecutionTimeoutEnforcingThread.IsBackground = true;
147 ExecutionTimeoutEnforcingThread.Start();
148 }
149
137 // 150 //
138 // Start event queue processing threads (worker threads) 151 // Start event queue processing threads (worker threads)
139 // 152 //
140 for (int ThreadCount = 0; ThreadCount <= numberOfThreads; ThreadCount++) 153 lock (eventQueueThreadsLock)
141 { 154 {
142 Thread EventQueueThread = new Thread(EventQueueThreadLoop); 155 for (int ThreadCount = 0; ThreadCount <= numberOfThreads; ThreadCount++)
143 eventQueueThreads.Add(EventQueueThread); 156 {
144 EventQueueThread.IsBackground = true; 157 StartNewThreadClass();
145 EventQueueThread.Priority = ThreadPriority.BelowNormal; 158 }
146 EventQueueThread.Name = "EventQueueManagerThread_" + ThreadCount;
147 EventQueueThread.Start();
148 } 159 }
149 } 160 }
150 161
151 ~EventQueueManager() 162 ~EventQueueManager()
152 { 163 {
153 // Kill worker threads 164 try
154 foreach (Thread EventQueueThread in new ArrayList(eventQueueThreads))
155 { 165 {
156 if (EventQueueThread != null && EventQueueThread.IsAlive == true) 166 if (ExecutionTimeoutEnforcingThread != null)
157 { 167 {
158 try 168 if (ExecutionTimeoutEnforcingThread.IsAlive)
159 {
160 EventQueueThread.Abort();
161 EventQueueThread.Join();
162 }
163 catch (Exception)
164 { 169 {
165 //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Exception killing worker thread: " + e.ToString()); 170 ExecutionTimeoutEnforcingThread.Abort();
166 } 171 }
167 } 172 }
168 } 173 }
169 eventQueueThreads.Clear(); 174 catch (Exception ex)
170 // Todo: Clean up our queues
171 eventQueue.Clear();
172 }
173
174 /// <summary>
175 /// Queue processing thread loop
176 /// </summary>
177 private void EventQueueThreadLoop()
178 {
179 //myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Worker thread spawned");
180 try
181 { 175 {
182 QueueItemStruct BlankQIS = new QueueItemStruct(); 176 }
183 while (true)
184 {
185 try
186 {
187 QueueItemStruct QIS = BlankQIS;
188 bool GotItem = false;
189 177
190 if (eventQueue.Count == 0)
191 {
192 // Nothing to do? Sleep a bit waiting for something to do
193 Thread.Sleep(nothingToDoSleepms);
194 }
195 else
196 {
197 // Something in queue, process
198 //myScriptEngine.m_logger.Verbose("ScriptEngine", "Processing event for localID: " + QIS.localID + ", itemID: " + QIS.itemID + ", FunctionName: " + QIS.FunctionName);
199 178
200 // OBJECT BASED LOCK - TWO THREADS WORKING ON SAME OBJECT IS NOT GOOD 179 // Kill worker threads
201 lock (queueLock) 180 lock (eventQueueThreadsLock)
202 {
203 GotItem = false;
204 for (int qc = 0; qc < eventQueue.Count; qc++)
205 {
206 // Get queue item
207 QIS = eventQueue.Dequeue();
208
209 // Check if object is being processed by someone else
210 if (TryLock(QIS.localID) == false)
211 {
212 // Object is already being processed, requeue it
213 eventQueue.Enqueue(QIS);
214 }
215 else
216 {
217 // We have lock on an object and can process it
218 GotItem = true;
219 break;
220 }
221 } // go through queue
222 } // lock
223
224 if (GotItem == true)
225 {
226 // Execute function
227 try
228 {
229#if DEBUG
230 m_ScriptEngine.Log.Debug("ScriptEngine", "Executing event:\r\n"
231 + "QIS.localID: " + QIS.localID
232 + ", QIS.itemID: " + QIS.itemID
233 + ", QIS.functionName: " + QIS.functionName);
234#endif
235 m_ScriptEngine.m_ScriptManager.ExecuteEvent(QIS.localID, QIS.itemID,
236 QIS.functionName, QIS.llDetectParams, QIS.param);
237 }
238 catch (Exception e)
239 {
240 // DISPLAY ERROR INWORLD
241 string text = "Error executing script function \"" + QIS.functionName + "\":\r\n";
242 if (e.InnerException != null)
243 {
244 // Send inner exception
245 text += e.InnerException.Message.ToString();
246 }
247 else
248 {
249 text += "\r\n";
250 // Send normal
251 text += e.Message.ToString();
252 }
253 try
254 {
255 if (text.Length > 1500)
256 text = text.Substring(0, 1500);
257 IScriptHost m_host = m_ScriptEngine.World.GetSceneObjectPart(QIS.localID);
258 //if (m_host != null)
259 //{
260 m_ScriptEngine.World.SimChat(Helpers.StringToField(text), ChatTypeEnum.Say, 0,
261 m_host.AbsolutePosition, m_host.Name, m_host.UUID);
262 }
263 catch
264 {
265 //}
266 //else
267 //{
268 // T oconsole
269 m_ScriptEngine.Log.Error("ScriptEngine",
270 "Unable to send text in-world:\r\n" + text);
271 }
272 }
273 finally
274 {
275 ReleaseLock(QIS.localID);
276 }
277 }
278 } // Something in queue
279 }
280 catch (ThreadAbortException tae)
281 {
282 throw tae;
283 }
284 catch (Exception e)
285 {
286 m_ScriptEngine.Log.Error("ScriptEngine", "Exception in EventQueueThreadLoop: " + e.ToString());
287 }
288 } // while
289 } // try
290 catch (ThreadAbortException)
291 { 181 {
292 //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Worker thread killed: " + tae.Message); 182 foreach (EventQueueThreadClass EventQueueThread in new ArrayList(eventQueueThreads))
183 {
184 EventQueueThread.Shutdown();
185 }
186 eventQueueThreads.Clear();
293 } 187 }
188 // Todo: Clean up our queues
189 eventQueue.Clear();
294 } 190 }
295 191
192
296 /// <summary> 193 /// <summary>
297 /// Try to get a mutex lock on localID 194 /// Try to get a mutex lock on localID
298 /// </summary> 195 /// </summary>
299 /// <param name="localID"></param> 196 /// <param name="localID"></param>
300 /// <returns></returns> 197 /// <returns></returns>
301 private bool TryLock(uint localID) 198 public bool TryLock(uint localID)
302 { 199 {
303 lock (tryLockLock) 200 lock (tryLockLock)
304 { 201 {
@@ -318,7 +215,7 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
318 /// Release mutex lock on localID 215 /// Release mutex lock on localID
319 /// </summary> 216 /// </summary>
320 /// <param name="localID"></param> 217 /// <param name="localID"></param>
321 private void ReleaseLock(uint localID) 218 public void ReleaseLock(uint localID)
322 { 219 {
323 lock (tryLockLock) 220 lock (tryLockLock)
324 { 221 {
@@ -383,5 +280,58 @@ namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
383 eventQueue.Enqueue(QIS); 280 eventQueue.Enqueue(QIS);
384 } 281 }
385 } 282 }
283
284 /// <summary>
285 /// A thread should run in this loop and check all running scripts
286 /// </summary>
287 public void ExecutionTimeoutEnforcingLoop()
288 {
289 try
290 {
291 while (true)
292 {
293 System.Threading.Thread.Sleep(maxFunctionExecutionTimems);
294 lock (eventQueueThreadsLock)
295 {
296 foreach (EventQueueThreadClass EventQueueThread in new ArrayList(eventQueueThreads))
297 {
298 if (EventQueueThread.InExecution)
299 {
300 if (DateTime.Now.Subtract(EventQueueThread.LastExecutionStarted).Milliseconds >
301 maxFunctionExecutionTimems)
302 {
303 // We need to kill this thread!
304 AbortThreadClass(EventQueueThread);
305 // Then start another
306 StartNewThreadClass();
307 }
308 }
309 }
310 }
311 }
312 }
313 catch (ThreadAbortException tae)
314 {
315 }
316 }
317
318 private static void AbortThreadClass(EventQueueThreadClass threadClass)
319 {
320 try
321 {
322 threadClass.Shutdown();
323 }
324 catch (Exception ex)
325 {
326 Console.WriteLine("Could you please report this to Tedd:");
327 Console.WriteLine("Script thread execution timeout kill ended in exception: " + ex.ToString());
328 }
329 }
330
331 private void StartNewThreadClass()
332 {
333 EventQueueThreadClass eqtc = new EventQueueThreadClass(this);
334 eventQueueThreads.Add(eqtc);
335 }
386 } 336 }
387} \ No newline at end of file 337} \ No newline at end of file
diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueThreadClass.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueThreadClass.cs
new file mode 100644
index 0000000..ad79fbc
--- /dev/null
+++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueThreadClass.cs
@@ -0,0 +1,201 @@
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Threading;
5using libsecondlife;
6using OpenSim.Framework;
7using OpenSim.Region.Environment.Scenes.Scripting;
8
9namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
10{
11 /// <summary>
12 /// Because every thread needs some data set for it (time started to execute current function), it will do its work within a class
13 /// </summary>
14 public class EventQueueThreadClass
15 {
16 /// <summary>
17 /// How many ms to sleep if queue is empty
18 /// </summary>
19 private int nothingToDoSleepms = 50;
20
21 public DateTime LastExecutionStarted;
22 public bool InExecution = false;
23
24 private EventQueueManager eventQueueManager;
25 public Thread EventQueueThread;
26 private static int ThreadCount = 0;
27
28 public EventQueueThreadClass(EventQueueManager eqm)
29 {
30 eventQueueManager = eqm;
31 Start();
32 }
33
34 ~EventQueueThreadClass()
35 {
36 Shutdown();
37 }
38
39 /// <summary>
40 /// Start thread
41 /// </summary>
42 private void Start()
43 {
44 EventQueueThread = new Thread(EventQueueThreadLoop);
45 EventQueueThread.IsBackground = true;
46 EventQueueThread.Priority = ThreadPriority.BelowNormal;
47 EventQueueThread.Name = "EventQueueManagerThread_" + ThreadCount;
48 EventQueueThread.Start();
49
50 // Look at this... Don't you wish everyone did that solid coding everywhere? :P
51 if (ThreadCount == int.MaxValue)
52 ThreadCount = 0;
53 ThreadCount++;
54 }
55
56 public void Shutdown()
57 {
58 if (EventQueueThread != null && EventQueueThread.IsAlive == true)
59 {
60 try
61 {
62 EventQueueThread.Abort();
63 EventQueueThread.Join();
64 }
65 catch (Exception)
66 {
67 //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Exception killing worker thread: " + e.ToString());
68 }
69 }
70 }
71
72
73 /// <summary>
74 /// Queue processing thread loop
75 /// </summary>
76 private void EventQueueThreadLoop()
77 {
78 //myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Worker thread spawned");
79 try
80 {
81 EventQueueManager.QueueItemStruct BlankQIS = new EventQueueManager.QueueItemStruct();
82 while (true)
83 {
84 try
85 {
86 EventQueueManager.QueueItemStruct QIS = BlankQIS;
87 bool GotItem = false;
88
89 if (eventQueueManager.eventQueue.Count == 0)
90 {
91 // Nothing to do? Sleep a bit waiting for something to do
92 Thread.Sleep(nothingToDoSleepms);
93 }
94 else
95 {
96 // Something in queue, process
97 //myScriptEngine.m_logger.Verbose("ScriptEngine", "Processing event for localID: " + QIS.localID + ", itemID: " + QIS.itemID + ", FunctionName: " + QIS.FunctionName);
98
99 // OBJECT BASED LOCK - TWO THREADS WORKING ON SAME OBJECT IS NOT GOOD
100 lock (eventQueueManager.queueLock)
101 {
102 GotItem = false;
103 for (int qc = 0; qc < eventQueueManager.eventQueue.Count; qc++)
104 {
105 // Get queue item
106 QIS = eventQueueManager.eventQueue.Dequeue();
107
108 // Check if object is being processed by someone else
109 if (eventQueueManager.TryLock(QIS.localID) == false)
110 {
111 // Object is already being processed, requeue it
112 eventQueueManager.eventQueue.Enqueue(QIS);
113 }
114 else
115 {
116 // We have lock on an object and can process it
117 GotItem = true;
118 break;
119 }
120 } // go through queue
121 } // lock
122
123 if (GotItem == true)
124 {
125 // Execute function
126 try
127 {
128#if DEBUG
129 eventQueueManager.m_ScriptEngine.Log.Debug("ScriptEngine", "Executing event:\r\n"
130 + "QIS.localID: " + QIS.localID
131 + ", QIS.itemID: " + QIS.itemID
132 + ", QIS.functionName: " + QIS.functionName);
133#endif
134 LastExecutionStarted = DateTime.Now;
135 InExecution = true;
136 eventQueueManager.m_ScriptEngine.m_ScriptManager.ExecuteEvent(QIS.localID, QIS.itemID,
137 QIS.functionName, QIS.llDetectParams, QIS.param);
138 InExecution = false;
139 }
140 catch (Exception e)
141 {
142 InExecution = false;
143 // DISPLAY ERROR INWORLD
144 string text = "Error executing script function \"" + QIS.functionName + "\":\r\n";
145 if (e.InnerException != null)
146 {
147 // Send inner exception
148 text += e.InnerException.Message.ToString();
149 }
150 else
151 {
152 text += "\r\n";
153 // Send normal
154 text += e.Message.ToString();
155 }
156 try
157 {
158 if (text.Length > 1500)
159 text = text.Substring(0, 1500);
160 IScriptHost m_host = eventQueueManager.m_ScriptEngine.World.GetSceneObjectPart(QIS.localID);
161 //if (m_host != null)
162 //{
163 eventQueueManager.m_ScriptEngine.World.SimChat(Helpers.StringToField(text), ChatTypeEnum.Say, 0,
164 m_host.AbsolutePosition, m_host.Name, m_host.UUID);
165 }
166 catch
167 {
168 //}
169 //else
170 //{
171 // T oconsole
172 eventQueueManager.m_ScriptEngine.Log.Error("ScriptEngine",
173 "Unable to send text in-world:\r\n" + text);
174 }
175 }
176 finally
177 {
178 InExecution = false;
179 eventQueueManager.ReleaseLock(QIS.localID);
180 }
181 }
182 } // Something in queue
183 }
184 catch (ThreadAbortException tae)
185 {
186 throw tae;
187 }
188 catch (Exception e)
189 {
190 eventQueueManager.m_ScriptEngine.Log.Error("ScriptEngine", "Exception in EventQueueThreadLoop: " + e.ToString());
191 }
192 } // while
193 } // try
194 catch (ThreadAbortException)
195 {
196 //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Worker thread killed: " + tae.Message);
197 }
198 }
199
200 }
201}