aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs
diff options
context:
space:
mode:
authorTedd Hansen2008-01-12 14:30:22 +0000
committerTedd Hansen2008-01-12 14:30:22 +0000
commitbacbade369a5244f9bcc611488b59f3bd4c8a564 (patch)
tree2cd909eff401066a69dba96615cbf736fdd73be5 /OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs
parent* Trying something to see if it helps teleports and border crossings (diff)
downloadopensim-SC-bacbade369a5244f9bcc611488b59f3bd4c8a564.zip
opensim-SC-bacbade369a5244f9bcc611488b59f3bd4c8a564.tar.gz
opensim-SC-bacbade369a5244f9bcc611488b59f3bd4c8a564.tar.bz2
opensim-SC-bacbade369a5244f9bcc611488b59f3bd4c8a564.tar.xz
Major reorganizing of DotNetEngine. Moved common script engine parts to ScriptEngine.Common, only .Net-specific code in DotNetEngine. AppDomains, event handling, event execution queue and multithreading, script load/unload queue, etc has been moved to ScriptEngine.Common.
Loads of things has been put into interfaces instead of the specific class. We are now one step closer to ScriptServer, and its very easy to implement new script languages. Just a few lines required to make them a OpenSim script module with all its glory.
Diffstat (limited to 'OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs')
-rw-r--r--OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs363
1 files changed, 363 insertions, 0 deletions
diff --git a/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs
new file mode 100644
index 0000000..62194df
--- /dev/null
+++ b/OpenSim/Region/ScriptEngine/Common/ScriptEngineBase/EventQueueManager.cs
@@ -0,0 +1,363 @@
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 OpenSim 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/* Original code: Tedd Hansen */
29using System;
30using System.Collections;
31using System.Collections.Generic;
32using System.Threading;
33using libsecondlife;
34using OpenSim.Framework;
35using OpenSim.Region.Environment.Scenes.Scripting;
36
37namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
38{
39 /// <summary>
40 /// EventQueueManager handles event queues
41 /// Events are queued and executed in separate thread
42 /// </summary>
43 [Serializable]
44 public class EventQueueManager
45 {
46
47 //
48 // Class is instanced in "ScriptEngine" and used by "EventManager" also instanced in "ScriptEngine".
49 //
50 // Class purpose is to queue and execute functions that are received by "EventManager":
51 // - allowing "EventManager" to release its event thread immediately, thus not interrupting server execution.
52 // - allowing us to prioritize and control execution of script functions.
53 // Class can use multiple threads for simultaneous execution. Mutexes are used for thread safety.
54 //
55 // 1. Hold an execution queue for scripts
56 // 2. Use threads to process queue, each thread executes one script function on each pass.
57 // 3. Catch any script error and process it
58 //
59 //
60 // Notes:
61 // * Current execution load balancing is optimized for 1 thread, and can cause unfair execute balancing between scripts.
62 // Not noticeable unless server is under high load.
63 // * This class contains the number of threads used for script executions. Since we are not microthreading scripts yet,
64 // increase number of threads to allow more concurrent script executions in OpenSim.
65 //
66
67
68 /// <summary>
69 /// List of threads processing event queue
70 /// </summary>
71 private List<Thread> eventQueueThreads = new List<Thread>();
72
73 private object queueLock = new object(); // Mutex lock object
74
75 /// <summary>
76 /// How many ms to sleep if queue is empty
77 /// </summary>
78 private int nothingToDoSleepms = 50;
79
80 /// <summary>
81 /// How many threads to process queue with
82 /// </summary>
83 private int numberOfThreads = 2;
84
85 /// <summary>
86 /// Queue containing events waiting to be executed
87 /// </summary>
88 private Queue<QueueItemStruct> eventQueue = new Queue<QueueItemStruct>();
89
90 /// <summary>
91 /// Queue item structure
92 /// </summary>
93 private struct QueueItemStruct
94 {
95 public uint localID;
96 public LLUUID itemID;
97 public string functionName;
98 public object[] param;
99 }
100
101 /// <summary>
102 /// List of localID locks for mutex processing of script events
103 /// </summary>
104 private List<uint> objectLocks = new List<uint>();
105
106 private object tryLockLock = new object(); // Mutex lock object
107
108 private ScriptEngine m_ScriptEngine;
109
110 public EventQueueManager(ScriptEngine _ScriptEngine)
111 {
112 m_ScriptEngine = _ScriptEngine;
113
114 //
115 // Start event queue processing threads (worker threads)
116 //
117 for (int ThreadCount = 0; ThreadCount <= numberOfThreads; ThreadCount++)
118 {
119 Thread EventQueueThread = new Thread(EventQueueThreadLoop);
120 eventQueueThreads.Add(EventQueueThread);
121 EventQueueThread.IsBackground = true;
122 EventQueueThread.Priority = ThreadPriority.BelowNormal;
123 EventQueueThread.Name = "EventQueueManagerThread_" + ThreadCount;
124 EventQueueThread.Start();
125 }
126 }
127
128 ~EventQueueManager()
129 {
130 // Kill worker threads
131 foreach (Thread EventQueueThread in new ArrayList(eventQueueThreads))
132 {
133 if (EventQueueThread != null && EventQueueThread.IsAlive == true)
134 {
135 try
136 {
137 EventQueueThread.Abort();
138 EventQueueThread.Join();
139 }
140 catch (Exception)
141 {
142 //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Exception killing worker thread: " + e.ToString());
143 }
144 }
145 }
146 eventQueueThreads.Clear();
147 // Todo: Clean up our queues
148 eventQueue.Clear();
149 }
150
151 /// <summary>
152 /// Queue processing thread loop
153 /// </summary>
154 private void EventQueueThreadLoop()
155 {
156 //myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Worker thread spawned");
157 try
158 {
159 QueueItemStruct BlankQIS = new QueueItemStruct();
160 while (true)
161 {
162 try
163 {
164 QueueItemStruct QIS = BlankQIS;
165 bool GotItem = false;
166
167 if (eventQueue.Count == 0)
168 {
169 // Nothing to do? Sleep a bit waiting for something to do
170 Thread.Sleep(nothingToDoSleepms);
171 }
172 else
173 {
174 // Something in queue, process
175 //myScriptEngine.m_logger.Verbose("ScriptEngine", "Processing event for localID: " + QIS.localID + ", itemID: " + QIS.itemID + ", FunctionName: " + QIS.FunctionName);
176
177 // OBJECT BASED LOCK - TWO THREADS WORKING ON SAME OBJECT IS NOT GOOD
178 lock (queueLock)
179 {
180 GotItem = false;
181 for (int qc = 0; qc < eventQueue.Count; qc++)
182 {
183 // Get queue item
184 QIS = eventQueue.Dequeue();
185
186 // Check if object is being processed by someone else
187 if (TryLock(QIS.localID) == false)
188 {
189 // Object is already being processed, requeue it
190 eventQueue.Enqueue(QIS);
191 }
192 else
193 {
194 // We have lock on an object and can process it
195 GotItem = true;
196 break;
197 }
198 } // go through queue
199 } // lock
200
201 if (GotItem == true)
202 {
203 // Execute function
204 try
205 {
206#if DEBUG
207 m_ScriptEngine.Log.Debug("ScriptEngine", "Executing event:\r\n"
208 + "QIS.localID: " + QIS.localID
209 + ", QIS.itemID: " + QIS.itemID
210 + ", QIS.functionName: " + QIS.functionName);
211#endif
212 m_ScriptEngine.m_ScriptManager.ExecuteEvent(QIS.localID, QIS.itemID,
213 QIS.functionName, QIS.param);
214 }
215 catch (Exception e)
216 {
217 // DISPLAY ERROR INWORLD
218 string text = "Error executing script function \"" + QIS.functionName + "\":\r\n";
219 //if (e.InnerException != null)
220 //{
221 // Send inner exception
222 text += e.InnerException.Message.ToString();
223 //}
224 //else
225 //{
226 text += "\r\n";
227 // Send normal
228 text += e.Message.ToString();
229 //}
230 try
231 {
232 if (text.Length > 1500)
233 text = text.Substring(0, 1500);
234 IScriptHost m_host = m_ScriptEngine.World.GetSceneObjectPart(QIS.localID);
235 //if (m_host != null)
236 //{
237 m_ScriptEngine.World.SimChat(Helpers.StringToField(text), ChatTypeEnum.Say, 0,
238 m_host.AbsolutePosition, m_host.Name, m_host.UUID);
239 }
240 catch
241 {
242 //}
243 //else
244 //{
245 // T oconsole
246 m_ScriptEngine.Log.Error("ScriptEngine",
247 "Unable to send text in-world:\r\n" + text);
248 }
249 }
250 finally
251 {
252 ReleaseLock(QIS.localID);
253 }
254 }
255 } // Something in queue
256 }
257 catch (ThreadAbortException tae)
258 {
259 throw tae;
260 }
261 catch (Exception e)
262 {
263 m_ScriptEngine.Log.Error("ScriptEngine", "Exception in EventQueueThreadLoop: " + e.ToString());
264 }
265 } // while
266 } // try
267 catch (ThreadAbortException)
268 {
269 //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Worker thread killed: " + tae.Message);
270 }
271 }
272
273 /// <summary>
274 /// Try to get a mutex lock on localID
275 /// </summary>
276 /// <param name="localID"></param>
277 /// <returns></returns>
278 private bool TryLock(uint localID)
279 {
280 lock (tryLockLock)
281 {
282 if (objectLocks.Contains(localID) == true)
283 {
284 return false;
285 }
286 else
287 {
288 objectLocks.Add(localID);
289 return true;
290 }
291 }
292 }
293
294 /// <summary>
295 /// Release mutex lock on localID
296 /// </summary>
297 /// <param name="localID"></param>
298 private void ReleaseLock(uint localID)
299 {
300 lock (tryLockLock)
301 {
302 if (objectLocks.Contains(localID) == true)
303 {
304 objectLocks.Remove(localID);
305 }
306 }
307 }
308
309
310 /// <summary>
311 /// Add event to event execution queue
312 /// </summary>
313 /// <param name="localID"></param>
314 /// <param name="FunctionName">Name of the function, will be state + "_event_" + FunctionName</param>
315 /// <param name="param">Array of parameters to match event mask</param>
316 public void AddToObjectQueue(uint localID, string FunctionName, params object[] param)
317 {
318 // Determine all scripts in Object and add to their queue
319 //myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Adding localID: " + localID + ", FunctionName: " + FunctionName);
320
321
322 // Do we have any scripts in this object at all? If not, return
323 if (m_ScriptEngine.m_ScriptManager.Scripts.ContainsKey(localID) == false)
324 {
325 //Console.WriteLine("Event \"" + FunctionName + "\" for localID: " + localID + ". No scripts found on this localID.");
326 return;
327 }
328
329 Dictionary<LLUUID, IScript>.KeyCollection scriptKeys =
330 m_ScriptEngine.m_ScriptManager.GetScriptKeys(localID);
331
332 foreach (LLUUID itemID in scriptKeys)
333 {
334 // Add to each script in that object
335 // TODO: Some scripts may not subscribe to this event. Should we NOT add it? Does it matter?
336 AddToScriptQueue(localID, itemID, FunctionName, param);
337 }
338 }
339
340 /// <summary>
341 /// Add event to event execution queue
342 /// </summary>
343 /// <param name="localID"></param>
344 /// <param name="itemID"></param>
345 /// <param name="FunctionName">Name of the function, will be state + "_event_" + FunctionName</param>
346 /// <param name="param">Array of parameters to match event mask</param>
347 public void AddToScriptQueue(uint localID, LLUUID itemID, string FunctionName, params object[] param)
348 {
349 lock (queueLock)
350 {
351 // Create a structure and add data
352 QueueItemStruct QIS = new QueueItemStruct();
353 QIS.localID = localID;
354 QIS.itemID = itemID;
355 QIS.functionName = FunctionName;
356 QIS.param = param;
357
358 // Add it to queue
359 eventQueue.Enqueue(QIS);
360 }
361 }
362 }
363} \ No newline at end of file