diff options
Diffstat (limited to 'OpenSim/Region/ScriptEngine/DotNetEngine/EventQueueThreadClass.cs')
-rw-r--r-- | OpenSim/Region/ScriptEngine/DotNetEngine/EventQueueThreadClass.cs | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/EventQueueThreadClass.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/EventQueueThreadClass.cs new file mode 100644 index 0000000..db3f89f --- /dev/null +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/EventQueueThreadClass.cs | |||
@@ -0,0 +1,381 @@ | |||
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 | using System; | ||
29 | using System.Collections; | ||
30 | using System.Reflection; | ||
31 | using System.Text.RegularExpressions; | ||
32 | using System.Threading; | ||
33 | using System.Globalization; | ||
34 | using OpenMetaverse; | ||
35 | using log4net; | ||
36 | using OpenSim.Framework; | ||
37 | using OpenSim.Region.Environment.Scenes.Scripting; | ||
38 | |||
39 | namespace OpenSim.Region.ScriptEngine.DotNetEngine | ||
40 | { | ||
41 | /// <summary> | ||
42 | /// Because every thread needs some data set for it (time started to execute current function), it will do its work within a class | ||
43 | /// </summary> | ||
44 | public class EventQueueThreadClass : iScriptEngineFunctionModule | ||
45 | { | ||
46 | // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
47 | |||
48 | /// <summary> | ||
49 | /// How many ms to sleep if queue is empty | ||
50 | /// </summary> | ||
51 | private static int nothingToDoSleepms;// = 50; | ||
52 | private static ThreadPriority MyThreadPriority; | ||
53 | |||
54 | public long LastExecutionStarted; | ||
55 | public bool InExecution = false; | ||
56 | public bool KillCurrentScript = false; | ||
57 | |||
58 | //private EventQueueManager eventQueueManager; | ||
59 | public Thread EventQueueThread; | ||
60 | private static int ThreadCount = 0; | ||
61 | |||
62 | private string ScriptEngineName = "ScriptEngine.Common"; | ||
63 | |||
64 | public EventQueueThreadClass()//EventQueueManager eqm | ||
65 | { | ||
66 | //eventQueueManager = eqm; | ||
67 | ReadConfig(); | ||
68 | Start(); | ||
69 | } | ||
70 | |||
71 | ~EventQueueThreadClass() | ||
72 | { | ||
73 | Stop(); | ||
74 | } | ||
75 | |||
76 | public void ReadConfig() | ||
77 | { | ||
78 | lock (ScriptEngine.ScriptEngines) | ||
79 | { | ||
80 | foreach (ScriptEngine m_ScriptEngine in ScriptEngine.ScriptEngines) | ||
81 | { | ||
82 | ScriptEngineName = m_ScriptEngine.ScriptEngineName; | ||
83 | nothingToDoSleepms = m_ScriptEngine.ScriptConfigSource.GetInt("SleepTimeIfNoScriptExecutionMs", 50); | ||
84 | |||
85 | // Later with ScriptServer we might want to ask OS for stuff too, so doing this a bit manually | ||
86 | string pri = m_ScriptEngine.ScriptConfigSource.GetString("ScriptThreadPriority", "BelowNormal"); | ||
87 | switch (pri.ToLower()) | ||
88 | { | ||
89 | case "lowest": | ||
90 | MyThreadPriority = ThreadPriority.Lowest; | ||
91 | break; | ||
92 | case "belownormal": | ||
93 | MyThreadPriority = ThreadPriority.BelowNormal; | ||
94 | break; | ||
95 | case "normal": | ||
96 | MyThreadPriority = ThreadPriority.Normal; | ||
97 | break; | ||
98 | case "abovenormal": | ||
99 | MyThreadPriority = ThreadPriority.AboveNormal; | ||
100 | break; | ||
101 | case "highest": | ||
102 | MyThreadPriority = ThreadPriority.Highest; | ||
103 | break; | ||
104 | default: | ||
105 | MyThreadPriority = ThreadPriority.BelowNormal; // Default | ||
106 | m_ScriptEngine.Log.Error("[ScriptEngine.DotNetEngine]: Unknown priority type \"" + pri + | ||
107 | "\" in config file. Defaulting to \"BelowNormal\"."); | ||
108 | break; | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | // Now set that priority | ||
113 | if (EventQueueThread != null) | ||
114 | if (EventQueueThread.IsAlive) | ||
115 | EventQueueThread.Priority = MyThreadPriority; | ||
116 | } | ||
117 | |||
118 | /// <summary> | ||
119 | /// Start thread | ||
120 | /// </summary> | ||
121 | private void Start() | ||
122 | { | ||
123 | EventQueueThread = new Thread(EventQueueThreadLoop); | ||
124 | EventQueueThread.IsBackground = true; | ||
125 | |||
126 | EventQueueThread.Priority = MyThreadPriority; | ||
127 | EventQueueThread.Name = "EventQueueManagerThread_" + ThreadCount; | ||
128 | EventQueueThread.Start(); | ||
129 | ThreadTracker.Add(EventQueueThread); | ||
130 | |||
131 | // Look at this... Don't you wish everyone did that solid coding everywhere? :P | ||
132 | if (ThreadCount == int.MaxValue) | ||
133 | ThreadCount = 0; | ||
134 | ThreadCount++; | ||
135 | } | ||
136 | |||
137 | public void Stop() | ||
138 | { | ||
139 | //PleaseShutdown = true; // Set shutdown flag | ||
140 | //Thread.Sleep(100); // Wait a bit | ||
141 | if (EventQueueThread != null && EventQueueThread.IsAlive == true) | ||
142 | { | ||
143 | try | ||
144 | { | ||
145 | EventQueueThread.Abort(); // Send abort | ||
146 | //EventQueueThread.Join(); // Wait for it | ||
147 | } | ||
148 | catch (Exception) | ||
149 | { | ||
150 | //myScriptEngine.Log.Info("[" + ScriptEngineName + "]: EventQueueManager Exception killing worker thread: " + e.ToString()); | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | |||
155 | private EventQueueManager.QueueItemStruct BlankQIS = new EventQueueManager.QueueItemStruct(); | ||
156 | private ScriptEngine lastScriptEngine; | ||
157 | /// <summary> | ||
158 | /// Queue processing thread loop | ||
159 | /// </summary> | ||
160 | private void EventQueueThreadLoop() | ||
161 | { | ||
162 | CultureInfo USCulture = new CultureInfo("en-US"); | ||
163 | Thread.CurrentThread.CurrentCulture = USCulture; | ||
164 | |||
165 | //myScriptEngine.Log.Info("[" + ScriptEngineName + "]: EventQueueManager Worker thread spawned"); | ||
166 | try | ||
167 | { | ||
168 | while (true) | ||
169 | { | ||
170 | try | ||
171 | { | ||
172 | while (true) | ||
173 | { | ||
174 | DoProcessQueue(); | ||
175 | } | ||
176 | } | ||
177 | catch (ThreadAbortException) | ||
178 | { | ||
179 | if (lastScriptEngine != null) | ||
180 | lastScriptEngine.Log.Info("[" + ScriptEngineName + "]: ThreadAbortException while executing function."); | ||
181 | } | ||
182 | catch (Exception e) | ||
183 | { | ||
184 | if (lastScriptEngine != null) | ||
185 | lastScriptEngine.Log.Error("[" + ScriptEngineName + "]: Exception in EventQueueThreadLoop: " + e.ToString()); | ||
186 | } | ||
187 | } | ||
188 | } | ||
189 | catch (ThreadAbortException) | ||
190 | { | ||
191 | //myScriptEngine.Log.Info("[" + ScriptEngineName + "]: EventQueueManager Worker thread killed: " + tae.Message); | ||
192 | } | ||
193 | } | ||
194 | |||
195 | public void DoProcessQueue() | ||
196 | { | ||
197 | //lock (ScriptEngine.ScriptEngines) | ||
198 | //{ | ||
199 | foreach (ScriptEngine m_ScriptEngine in new ArrayList(ScriptEngine.ScriptEngines)) | ||
200 | { | ||
201 | lastScriptEngine = m_ScriptEngine; | ||
202 | // Every now and then check if we should shut down | ||
203 | //if (PleaseShutdown || EventQueueManager.ThreadsToExit > 0) | ||
204 | //{ | ||
205 | // // Someone should shut down, lets get exclusive lock | ||
206 | // lock (EventQueueManager.ThreadsToExitLock) | ||
207 | // { | ||
208 | // // Lets re-check in case someone grabbed it | ||
209 | // if (EventQueueManager.ThreadsToExit > 0) | ||
210 | // { | ||
211 | // // Its crowded here so we'll shut down | ||
212 | // EventQueueManager.ThreadsToExit--; | ||
213 | // Stop(); | ||
214 | // return; | ||
215 | // } | ||
216 | // else | ||
217 | // { | ||
218 | // // We have been asked to shut down | ||
219 | // Stop(); | ||
220 | // return; | ||
221 | // } | ||
222 | // } | ||
223 | //} | ||
224 | |||
225 | //try | ||
226 | // { | ||
227 | EventQueueManager.QueueItemStruct QIS = BlankQIS; | ||
228 | bool GotItem = false; | ||
229 | |||
230 | //if (PleaseShutdown) | ||
231 | // return; | ||
232 | |||
233 | if (m_ScriptEngine.m_EventQueueManager == null || m_ScriptEngine.m_EventQueueManager.eventQueue == null) | ||
234 | continue; | ||
235 | |||
236 | if (m_ScriptEngine.m_EventQueueManager.eventQueue.Count == 0) | ||
237 | { | ||
238 | // Nothing to do? Sleep a bit waiting for something to do | ||
239 | Thread.Sleep(nothingToDoSleepms); | ||
240 | } | ||
241 | else | ||
242 | { | ||
243 | // Something in queue, process | ||
244 | //myScriptEngine.Log.Info("[" + ScriptEngineName + "]: Processing event for localID: " + QIS.localID + ", itemID: " + QIS.itemID + ", FunctionName: " + QIS.FunctionName); | ||
245 | |||
246 | // OBJECT BASED LOCK - TWO THREADS WORKING ON SAME OBJECT IS NOT GOOD | ||
247 | lock (m_ScriptEngine.m_EventQueueManager.eventQueue) | ||
248 | { | ||
249 | GotItem = false; | ||
250 | for (int qc = 0; qc < m_ScriptEngine.m_EventQueueManager.eventQueue.Count; qc++) | ||
251 | { | ||
252 | // Get queue item | ||
253 | QIS = m_ScriptEngine.m_EventQueueManager.eventQueue.Dequeue(); | ||
254 | |||
255 | // Check if object is being processed by someone else | ||
256 | if (m_ScriptEngine.m_EventQueueManager.TryLock(QIS.localID) == false) | ||
257 | { | ||
258 | // Object is already being processed, requeue it | ||
259 | m_ScriptEngine.m_EventQueueManager.eventQueue.Enqueue(QIS); | ||
260 | } | ||
261 | else | ||
262 | { | ||
263 | // We have lock on an object and can process it | ||
264 | GotItem = true; | ||
265 | break; | ||
266 | } | ||
267 | } | ||
268 | } | ||
269 | |||
270 | if (GotItem == true) | ||
271 | { | ||
272 | // Execute function | ||
273 | try | ||
274 | { | ||
275 | ///cfk 2-7-08 dont need this right now and the default Linux build has DEBUG defined | ||
276 | #if DEBUG | ||
277 | //eventQueueManager.m_ScriptEngine.Log.Debug("[" + ScriptEngineName + "]: " + | ||
278 | // "Executing event:\r\n" | ||
279 | // + "QIS.localID: " + QIS.localID | ||
280 | // + ", QIS.itemID: " + QIS.itemID | ||
281 | // + ", QIS.functionName: " + | ||
282 | // QIS.functionName); | ||
283 | #endif | ||
284 | // Only pipe event if land supports it. | ||
285 | if (m_ScriptEngine.World.PipeEventsForScript(QIS.localID)) | ||
286 | { | ||
287 | LastExecutionStarted = DateTime.Now.Ticks; | ||
288 | KillCurrentScript = false; | ||
289 | InExecution = true; | ||
290 | m_ScriptEngine.m_ScriptManager.ExecuteEvent(QIS.localID, | ||
291 | QIS.itemID, | ||
292 | QIS.functionName, | ||
293 | QIS.llDetectParams, | ||
294 | QIS.param); | ||
295 | InExecution = false; | ||
296 | } | ||
297 | } | ||
298 | catch (Exception e) | ||
299 | { | ||
300 | InExecution = false; | ||
301 | // DISPLAY ERROR INWORLD | ||
302 | string text = "Error executing script function \"" + QIS.functionName + | ||
303 | "\":\r\n"; | ||
304 | if (e.InnerException != null) | ||
305 | { | ||
306 | // Send inner exception | ||
307 | string line = " (unknown line)"; | ||
308 | Regex rx = new Regex(@"SecondLife\.Script\..+[\s:](?<line>\d+)\.?\r?$", RegexOptions.Compiled); | ||
309 | if (rx.Match(e.InnerException.ToString()).Success) | ||
310 | line = " (line " + rx.Match(e.InnerException.ToString()).Result("${line}") + ")"; | ||
311 | text += e.InnerException.Message.ToString() + line; | ||
312 | } | ||
313 | else | ||
314 | { | ||
315 | text += "\r\n"; | ||
316 | // Send normal | ||
317 | text += e.Message.ToString(); | ||
318 | } | ||
319 | if (KillCurrentScript) | ||
320 | text += "\r\nScript will be deactivated!"; | ||
321 | |||
322 | try | ||
323 | { | ||
324 | if (text.Length > 1500) | ||
325 | text = text.Substring(0, 1500); | ||
326 | IScriptHost m_host = | ||
327 | m_ScriptEngine.World.GetSceneObjectPart(QIS.localID); | ||
328 | //if (m_host != null) | ||
329 | //{ | ||
330 | m_ScriptEngine.World.SimChat(Utils.StringToBytes(text), | ||
331 | ChatTypeEnum.DebugChannel, 2147483647, | ||
332 | m_host.AbsolutePosition, | ||
333 | m_host.Name, m_host.UUID, false); | ||
334 | } | ||
335 | catch (Exception) | ||
336 | { | ||
337 | //} | ||
338 | //else | ||
339 | //{ | ||
340 | // T oconsole | ||
341 | m_ScriptEngine.m_EventQueueManager.m_ScriptEngine.Log.Error("[" + ScriptEngineName + | ||
342 | "]: " + | ||
343 | "Unable to send text in-world:\r\n" + | ||
344 | text); | ||
345 | } | ||
346 | finally | ||
347 | { | ||
348 | // So we are done sending message in-world | ||
349 | if (KillCurrentScript) | ||
350 | { | ||
351 | m_ScriptEngine.m_EventQueueManager.m_ScriptEngine.m_ScriptManager.StopScript( | ||
352 | QIS.localID, QIS.itemID); | ||
353 | } | ||
354 | } | ||
355 | |||
356 | // Pass it on so it's displayed on the console | ||
357 | // and in the logs (mikem 2008.06.02). | ||
358 | throw e.InnerException; | ||
359 | } | ||
360 | finally | ||
361 | { | ||
362 | InExecution = false; | ||
363 | m_ScriptEngine.m_EventQueueManager.ReleaseLock(QIS.localID); | ||
364 | } | ||
365 | } | ||
366 | } | ||
367 | } | ||
368 | // } | ||
369 | } | ||
370 | |||
371 | ///// <summary> | ||
372 | ///// If set to true then threads and stuff should try to make a graceful exit | ||
373 | ///// </summary> | ||
374 | //public bool PleaseShutdown | ||
375 | //{ | ||
376 | // get { return _PleaseShutdown; } | ||
377 | // set { _PleaseShutdown = value; } | ||
378 | //} | ||
379 | //private bool _PleaseShutdown = false; | ||
380 | } | ||
381 | } | ||