diff options
Diffstat (limited to 'OpenSim/Region/ScriptEngine/XMREngine/XMRScriptUThread.cs')
-rw-r--r-- | OpenSim/Region/ScriptEngine/XMREngine/XMRScriptUThread.cs | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRScriptUThread.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRScriptUThread.cs new file mode 100644 index 0000000..2e290dd --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRScriptUThread.cs | |||
@@ -0,0 +1,557 @@ | |||
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 Mono.Tasklets; | ||
29 | using System; | ||
30 | using System.Collections.Generic; | ||
31 | using System.Reflection; | ||
32 | using System.Threading; | ||
33 | |||
34 | |||
35 | |||
36 | /***************************\ | ||
37 | * Use standard C# code * | ||
38 | * - uses system threads * | ||
39 | \***************************/ | ||
40 | |||
41 | namespace OpenSim.Region.ScriptEngine.XMREngine { | ||
42 | |||
43 | public class ScriptUThread_Sys : IScriptUThread, IDisposable | ||
44 | { | ||
45 | private Exception except; | ||
46 | private int active; // -1: hibernating | ||
47 | // 0: exited | ||
48 | // 1: running | ||
49 | private object activeLock = new object (); | ||
50 | private XMRInstance instance; | ||
51 | |||
52 | public ScriptUThread_Sys (XMRInstance instance) | ||
53 | { | ||
54 | this.instance = instance; | ||
55 | } | ||
56 | |||
57 | /** | ||
58 | * @brief Start script event handler from the beginning. | ||
59 | * Return when either the script event handler completes | ||
60 | * or the script calls Hiber(). | ||
61 | * @returns null: script did not throw any exception so far | ||
62 | * else: script threw an exception | ||
63 | */ | ||
64 | public Exception StartEx () | ||
65 | { | ||
66 | lock (activeLock) { | ||
67 | |||
68 | /* | ||
69 | * We should only be called when script is inactive. | ||
70 | */ | ||
71 | if (active != 0) throw new Exception ("active=" + active); | ||
72 | |||
73 | /* | ||
74 | * Tell CallSEHThread() to run script event handler in a thread. | ||
75 | */ | ||
76 | active = 1; | ||
77 | TredPoo.RunSomething (CallSEHThread); | ||
78 | |||
79 | /* | ||
80 | * Wait for script to call Hiber() or for script to | ||
81 | * return back out to CallSEHThread(). | ||
82 | */ | ||
83 | while (active > 0) { | ||
84 | Monitor.Wait (activeLock); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | /* | ||
89 | * Return whether or not script threw an exception. | ||
90 | */ | ||
91 | return except; | ||
92 | } | ||
93 | |||
94 | /** | ||
95 | * @brief We now want to run some more script code from where it last hibernated | ||
96 | * until it either finishes the script event handler or until the script | ||
97 | * calls Hiber() again. | ||
98 | */ | ||
99 | public Exception ResumeEx () | ||
100 | { | ||
101 | lock (activeLock) { | ||
102 | |||
103 | /* | ||
104 | * We should only be called when script is hibernating. | ||
105 | */ | ||
106 | if (active >= 0) throw new Exception ("active=" + active); | ||
107 | |||
108 | /* | ||
109 | * Tell Hiber() to return back to script. | ||
110 | */ | ||
111 | active = 1; | ||
112 | Monitor.PulseAll (activeLock); | ||
113 | |||
114 | /* | ||
115 | * Wait for script to call Hiber() again or for script to | ||
116 | * return back out to CallSEHThread(). | ||
117 | */ | ||
118 | while (active > 0) { | ||
119 | Monitor.Wait (activeLock); | ||
120 | } | ||
121 | } | ||
122 | |||
123 | /* | ||
124 | * Return whether or not script threw an exception. | ||
125 | */ | ||
126 | return except; | ||
127 | } | ||
128 | |||
129 | /** | ||
130 | * @brief Script is being closed out. | ||
131 | * Terminate thread asap. | ||
132 | */ | ||
133 | public void Dispose () | ||
134 | { | ||
135 | lock (activeLock) { | ||
136 | instance = null; | ||
137 | Monitor.PulseAll (activeLock); | ||
138 | } | ||
139 | } | ||
140 | |||
141 | /** | ||
142 | * @brief Determine if script is active. | ||
143 | * Returns: 0: nothing started or has returned | ||
144 | * Resume() must not be called | ||
145 | * Start() may be called | ||
146 | * Hiber() must not be called | ||
147 | * -1: thread has called Hiber() | ||
148 | * Resume() may be called | ||
149 | * Start() may be called | ||
150 | * Hiber() must not be called | ||
151 | * 1: thread is running | ||
152 | * Resume() must not be called | ||
153 | * Start() must not be called | ||
154 | * Hiber() may be called | ||
155 | */ | ||
156 | public int Active () | ||
157 | { | ||
158 | return active; | ||
159 | } | ||
160 | |||
161 | /** | ||
162 | * @brief This thread executes the script event handler code. | ||
163 | */ | ||
164 | private void CallSEHThread () | ||
165 | { | ||
166 | lock (activeLock) { | ||
167 | if (active <= 0) throw new Exception ("active=" + active); | ||
168 | |||
169 | except = null; // assume completion without exception | ||
170 | try { | ||
171 | instance.CallSEH (); // run script event handler | ||
172 | } catch (Exception e) { | ||
173 | except = e; // threw exception, save for Start()/Resume() | ||
174 | } | ||
175 | |||
176 | active = 0; // tell Start() or Resume() we're done | ||
177 | Monitor.PulseAll (activeLock); | ||
178 | } | ||
179 | } | ||
180 | |||
181 | /** | ||
182 | * @brief Called by the script event handler whenever it wants to hibernate. | ||
183 | */ | ||
184 | public void Hiber () | ||
185 | { | ||
186 | if (active <= 0) throw new Exception ("active=" + active); | ||
187 | |||
188 | // tell Start() or Resume() we are hibernating | ||
189 | active = -1; | ||
190 | Monitor.PulseAll (activeLock); | ||
191 | |||
192 | // wait for Resume() or Dispose() to be called | ||
193 | while ((active < 0) && (instance != null)) { | ||
194 | Monitor.Wait (activeLock); | ||
195 | } | ||
196 | |||
197 | // don't execute any more script code, just exit | ||
198 | if (instance == null) { | ||
199 | throw new AbortedByDisposeException (); | ||
200 | } | ||
201 | } | ||
202 | |||
203 | /** | ||
204 | * @brief Number of remaining stack bytes. | ||
205 | */ | ||
206 | public int StackLeft () | ||
207 | { | ||
208 | return 0x7FFFFFFF; | ||
209 | } | ||
210 | |||
211 | public class AbortedByDisposeException : Exception, IXMRUncatchable { } | ||
212 | |||
213 | /** | ||
214 | * @brief Pool of threads that run script event handlers. | ||
215 | */ | ||
216 | private class TredPoo { | ||
217 | private static readonly TimeSpan idleTimeSpan = new TimeSpan (0, 0, 1, 0, 0); // 1 minute | ||
218 | |||
219 | private static int tredPooAvail = 0; | ||
220 | private static object tredPooLock = new object (); | ||
221 | private static Queue<ThreadStart> tredPooQueue = new Queue<ThreadStart> (); | ||
222 | |||
223 | /** | ||
224 | * @brief Queue a function for execution in a system thread. | ||
225 | */ | ||
226 | public static void RunSomething (ThreadStart entry) | ||
227 | { | ||
228 | lock (tredPooLock) { | ||
229 | tredPooQueue.Enqueue (entry); | ||
230 | Monitor.Pulse (tredPooLock); | ||
231 | if (tredPooAvail < tredPooQueue.Count) { | ||
232 | new TredPoo (); | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | |||
237 | /** | ||
238 | * @brief Start a new system thread. | ||
239 | * It will shortly attempt to dequeue work or if none, | ||
240 | * add itself to the available thread list. | ||
241 | */ | ||
242 | private TredPoo () | ||
243 | { | ||
244 | Thread thread = new Thread (Main); | ||
245 | thread.Name = "XMRUThread_sys"; | ||
246 | thread.IsBackground = true; | ||
247 | thread.Start (); | ||
248 | tredPooAvail ++; | ||
249 | } | ||
250 | |||
251 | /** | ||
252 | * @brief Executes items from the queue or waits a little while | ||
253 | * if nothing. If idle for a while, it exits. | ||
254 | */ | ||
255 | private void Main () | ||
256 | { | ||
257 | int first = 1; | ||
258 | ThreadStart entry; | ||
259 | while (true) { | ||
260 | lock (tredPooLock) { | ||
261 | tredPooAvail -= first; | ||
262 | first = 0; | ||
263 | while (tredPooQueue.Count <= 0) { | ||
264 | tredPooAvail ++; | ||
265 | bool keepgoing = Monitor.Wait (tredPooLock, idleTimeSpan); | ||
266 | -- tredPooAvail; | ||
267 | if (!keepgoing) return; | ||
268 | } | ||
269 | entry = tredPooQueue.Dequeue (); | ||
270 | } | ||
271 | entry (); | ||
272 | } | ||
273 | } | ||
274 | } | ||
275 | } | ||
276 | } | ||
277 | |||
278 | |||
279 | |||
280 | /*************************************\ | ||
281 | * Use Mono.Tasklets.Continuations * | ||
282 | * - memcpy's stack * | ||
283 | \*************************************/ | ||
284 | |||
285 | namespace OpenSim.Region.ScriptEngine.XMREngine { | ||
286 | |||
287 | public partial class XMRInstance { | ||
288 | public Mono.Tasklets.Continuation engstack; | ||
289 | public Mono.Tasklets.Continuation scrstack; | ||
290 | } | ||
291 | |||
292 | public class ScriptUThread_Con : IScriptUThread, IDisposable | ||
293 | { | ||
294 | private XMRInstance instance; | ||
295 | |||
296 | public ScriptUThread_Con (XMRInstance instance) | ||
297 | { | ||
298 | this.instance = instance; | ||
299 | } | ||
300 | |||
301 | private const int SAVEENGINESTACK = 0; | ||
302 | private const int LOADENGINESTACK = 1; | ||
303 | private const int SAVESCRIPTSTACK = 2; | ||
304 | private const int LOADSCRIPTSTACK = 3; | ||
305 | |||
306 | private Exception except; | ||
307 | private int active; | ||
308 | |||
309 | /** | ||
310 | * @brief Start script event handler from the beginning. | ||
311 | * Return when either the script event handler completes | ||
312 | * or the script calls Hiber(). | ||
313 | * @returns null: script did not throw any exception so far | ||
314 | * else: script threw an exception | ||
315 | */ | ||
316 | public Exception StartEx () | ||
317 | { | ||
318 | /* | ||
319 | * Save engine stack so we know how to jump back to engine in case | ||
320 | * the script calls Hiber(). | ||
321 | */ | ||
322 | switch (instance.engstack.Store (SAVEENGINESTACK)) { | ||
323 | |||
324 | /* | ||
325 | * Engine stack has been saved, start running the event handler. | ||
326 | */ | ||
327 | case SAVEENGINESTACK: { | ||
328 | |||
329 | /* | ||
330 | * Run event handler according to stackFrames. | ||
331 | * In either case it is assumed that stateCode and eventCode | ||
332 | * indicate which event handler is to be called and that ehArgs | ||
333 | * points to the event handler argument list. | ||
334 | */ | ||
335 | active = 1; | ||
336 | except = null; | ||
337 | try { | ||
338 | instance.CallSEH (); | ||
339 | } catch (Exception e) { | ||
340 | except = e; | ||
341 | } | ||
342 | |||
343 | /* | ||
344 | * We now want to return to the script engine. | ||
345 | * Setting active = 0 means the microthread has exited. | ||
346 | * We need to call engstack.Restore() in case the script called Hiber() | ||
347 | * anywhere, we want to return out the corresponding Restore() and not the | ||
348 | * Start(). | ||
349 | */ | ||
350 | active = 0; | ||
351 | instance.engstack.Restore (LOADENGINESTACK); | ||
352 | throw new Exception ("returned from Restore()"); | ||
353 | } | ||
354 | |||
355 | /* | ||
356 | * Script called Hiber() somewhere so just return back out. | ||
357 | */ | ||
358 | case LOADENGINESTACK: { | ||
359 | break; | ||
360 | } | ||
361 | |||
362 | default: throw new Exception ("bad engstack code"); | ||
363 | } | ||
364 | |||
365 | return except; | ||
366 | } | ||
367 | |||
368 | public void Dispose () | ||
369 | { } | ||
370 | |||
371 | /** | ||
372 | * @brief Determine if script is active. | ||
373 | * Returns: 0: nothing started or has returned | ||
374 | * Resume() must not be called | ||
375 | * Start() may be called | ||
376 | * Hiber() must not be called | ||
377 | * -1: thread has called Hiber() | ||
378 | * Resume() may be called | ||
379 | * Start() may be called | ||
380 | * Hiber() must not be called | ||
381 | * 1: thread is running | ||
382 | * Resume() must not be called | ||
383 | * Start() must not be called | ||
384 | * Hiber() may be called | ||
385 | */ | ||
386 | public int Active () | ||
387 | { | ||
388 | return active; | ||
389 | } | ||
390 | |||
391 | /** | ||
392 | * @brief Called by the script wherever it wants to hibernate. | ||
393 | * So this means to save the scripts stack in 'instance.scrstack' then | ||
394 | * restore the engstack to cause us to return back to the engine. | ||
395 | */ | ||
396 | public void Hiber () | ||
397 | { | ||
398 | /* | ||
399 | * Save where we are in the script's code in 'instance.scrstack' | ||
400 | * so we can wake the script when Resume() is called. | ||
401 | */ | ||
402 | switch (instance.scrstack.Store (SAVESCRIPTSTACK)) { | ||
403 | |||
404 | /* | ||
405 | * Script's stack is now saved in 'instance.scrstack'. | ||
406 | * Reload the engine's stack from 'instance.engstack' and jump to it. | ||
407 | */ | ||
408 | case SAVESCRIPTSTACK: { | ||
409 | active = -1; | ||
410 | instance.engstack.Restore (LOADENGINESTACK); | ||
411 | throw new Exception ("returned from Restore()"); | ||
412 | } | ||
413 | |||
414 | /* | ||
415 | * Resume() was just called and we want to resume executing script code. | ||
416 | */ | ||
417 | case LOADSCRIPTSTACK: { | ||
418 | break; | ||
419 | } | ||
420 | |||
421 | default: throw new Exception ("bad scrstack code"); | ||
422 | } | ||
423 | } | ||
424 | |||
425 | /** | ||
426 | * @brief We now want to run some more script code from where it last hibernated | ||
427 | * until it either finishes the script event handler or until the script | ||
428 | * calls Hiber() again. | ||
429 | */ | ||
430 | public Exception ResumeEx () | ||
431 | { | ||
432 | /* | ||
433 | * Save where we are in the engine's code in 'instance.engstack' | ||
434 | * so if the script calls Hiber() again or exits, we know how to get | ||
435 | * back to the engine. | ||
436 | */ | ||
437 | switch (instance.engstack.Store (SAVEENGINESTACK)) { | ||
438 | |||
439 | /* | ||
440 | * This is original call to Resume() from the engine, | ||
441 | * jump to where we left off within Hiber(). | ||
442 | */ | ||
443 | case SAVEENGINESTACK: { | ||
444 | active = 1; | ||
445 | instance.scrstack.Restore (LOADSCRIPTSTACK); | ||
446 | throw new Exception ("returned from Restore()"); | ||
447 | } | ||
448 | |||
449 | /* | ||
450 | * Script has called Hiber() again, so return back to | ||
451 | * script engine code. | ||
452 | */ | ||
453 | case LOADENGINESTACK: { | ||
454 | break; | ||
455 | } | ||
456 | |||
457 | default: throw new Exception ("bad engstack code"); | ||
458 | } | ||
459 | |||
460 | return except; | ||
461 | } | ||
462 | |||
463 | /** | ||
464 | * @brief Number of remaining stack bytes. | ||
465 | */ | ||
466 | public int StackLeft () | ||
467 | { | ||
468 | return 0x7FFFFFFF; | ||
469 | } | ||
470 | } | ||
471 | } | ||
472 | |||
473 | |||
474 | |||
475 | /***********************************\ | ||
476 | * Use Mono.Tasklets.MMRUThreads * | ||
477 | * - switches stack pointer * | ||
478 | \***********************************/ | ||
479 | |||
480 | namespace OpenSim.Region.ScriptEngine.XMREngine { | ||
481 | |||
482 | public class ScriptUThread_MMR : IScriptUThread, IDisposable | ||
483 | { | ||
484 | private static Exception uthread_looked; | ||
485 | private static Type uttype; | ||
486 | private static Type uthread_entry; | ||
487 | private static MethodInfo uthread_dispose; | ||
488 | private static MethodInfo uthread_startex; | ||
489 | private static MethodInfo uthread_resumex; | ||
490 | private static MethodInfo uthread_suspend; | ||
491 | private static MethodInfo uthread_active; | ||
492 | private static MethodInfo uthread_stackleft; | ||
493 | |||
494 | public static Exception LoadMono () | ||
495 | { | ||
496 | if ((uthread_looked == null) && (uthread_stackleft == null)) { | ||
497 | try { | ||
498 | Assembly mt = Assembly.Load ("Mono.Tasklets"); | ||
499 | uttype = mt.GetType ("Mono.Tasklets.MMRUThread", true); | ||
500 | uthread_entry = mt.GetType ("Mono.Tasklets.MMRUThread+Entry", true); | ||
501 | |||
502 | uthread_dispose = uttype.GetMethod ("Dispose"); // no parameters, no return value | ||
503 | uthread_startex = uttype.GetMethod ("StartEx"); // takes uthread_entry delegate as parameter, returns exception | ||
504 | uthread_resumex = uttype.GetMethod ("ResumeEx"); // takes exception as parameter, returns exception | ||
505 | uthread_suspend = uttype.GetMethod ("Suspend", new Type[] { }); // no return value | ||
506 | uthread_active = uttype.GetMethod ("Active"); // no parameters, returns int | ||
507 | uthread_stackleft = uttype.GetMethod ("StackLeft"); // no parameters, returns IntPtr | ||
508 | } catch (Exception e) { | ||
509 | uthread_looked = new NotSupportedException ("'mmr' thread model requires patched mono", e); | ||
510 | } | ||
511 | } | ||
512 | return uthread_looked; | ||
513 | } | ||
514 | |||
515 | private static object[] resumex_args = new object[] { null }; | ||
516 | |||
517 | private object uthread; // type MMRUThread | ||
518 | private object[] startex_args = new object[1]; | ||
519 | |||
520 | public ScriptUThread_MMR (XMRInstance instance) | ||
521 | { | ||
522 | this.uthread = Activator.CreateInstance (uttype, new object[] { (IntPtr) instance.m_StackSize, instance.m_DescName }); | ||
523 | startex_args[0] = Delegate.CreateDelegate (uthread_entry, instance, "CallSEH"); | ||
524 | } | ||
525 | |||
526 | public void Dispose () | ||
527 | { | ||
528 | uthread_dispose.Invoke (uthread, null); | ||
529 | uthread = null; | ||
530 | } | ||
531 | |||
532 | public Exception StartEx () | ||
533 | { | ||
534 | return (Exception) uthread_startex.Invoke (uthread, startex_args); | ||
535 | } | ||
536 | |||
537 | public Exception ResumeEx () | ||
538 | { | ||
539 | return (Exception) uthread_resumex.Invoke (uthread, resumex_args); | ||
540 | } | ||
541 | |||
542 | public void Hiber () | ||
543 | { | ||
544 | uthread_suspend.Invoke (null, null); | ||
545 | } | ||
546 | |||
547 | public int Active () | ||
548 | { | ||
549 | return (int) uthread_active.Invoke (uthread, null); | ||
550 | } | ||
551 | |||
552 | public int StackLeft () | ||
553 | { | ||
554 | return (int) (IntPtr) uthread_stackleft.Invoke (null, null); | ||
555 | } | ||
556 | } | ||
557 | } | ||