root/trunk/infrastructure/st/stackcontext.d

Revision 40, 51.5 kB (checked in by KirkMcDonald, 2 years ago)

Meta-programming library replaced; large re-write. StackThreads? updated to 0.3.2. Iteration updates.

Line 
1 /******************************************************
2  * StackThreads are userland, cooperative, lightweight
3  * threads. StackThreads are very efficient, requiring
4  * much less time per context switch than real threads.
5  * They also require far fewer resources than real
6  * threads, which allows many more StackThreads to exist
7  * simultaneously. In addition, StackThreads do not
8  * require explicit synchronization since they are
9  * non-preemptive.  There is no requirement that code
10  * be reentrant.
11  *
12  * This module implements the code necessary for context
13  * switching.  StackContexts can be used independently
14  * of StackThreads, and may be used for implementing
15  * coroutines, custom scheduling or complex iterators.
16  *
17  * Thanks to Lars Ivar Igesunde (larsivar@igesundes.no)
18  * for the ucontext bindings on Linux used in earlier
19  * implementations.
20  *
21  * Version: 0.12
22  * Date: October 17, 2006
23  * Authors: Mikola Lysenko, mclysenk@mtu.edu
24  * License: Use/copy/modify freely, just give credit.
25  * Copyright: Public domain.
26  *
27  * Bugs:
28  *  Debug builds will eat more stack space than release
29  *  builds.  To prevent this, you can allocate some
30  *  extra stack in debug mode.  This is not that tragic,
31  *  since overflows are now trapped.
32  *
33  *  DMD has a bug on linux with multiple delegates in a
34  *  scope.  Be aware that the linux version may have
35  *  issues due to a lack of proper testing.
36  *
37  *  Due to the way DMD handles windows exceptions, it is
38  *  impossible to trap for stack overflows.  Once this
39  *  gets fixed, it will be possible to allocate dynamic
40  *  stacks.
41  *
42  *  To prevent memory leaks, compile with -version=LEAK_FIX
43  *  This will slow down the application, but it will
44  *  improve memory usage.  In an ideal world, it would be
45  *  the default behavior, but due to issues with Phobos'
46  *  removeRange I have set it as optional.
47  *
48  *  GDC version does not support assembler optimizations, since
49  *  it uses a different calling convention.
50  *
51  * History:
52  *  v0.12 - Workaround for DMD bug.
53  *
54  *  v0.11 - Implementation is now thread safe.
55  *
56  *  v0.10 - Added the LEAK_FIX flag to work around the
57  *          slowness of std.gc.removeRange
58  *
59  *  v0.9 - Switched linux to an asm implementation.
60  *
61  *  v0.8 - Added throwYield.
62  *
63  *  v0.7 - Switched to system specific allocators
64  *      (VirtualAlloc, mmap) in order to catch stack
65  *      overflows.
66  *
67  *  v0.6 - Fixed a bug with the window version.  Now saves
68  *      EBX, ESI, EDI across switches.
69  *
70  *  v0.5 - Linux now fully supported.  Discovered the cause
71  *      of the exception problems: Bug in DMD.
72  *
73  *  v0.4 - Fixed the GC, added some linux support
74  *
75  *  v0.3 - Major refactoring
76  *
77  *  v0.2 - Fixed exception handling
78  *
79  *  v0.1 - Initial release
80  *
81  ******************************************************/
82 module st.stackcontext;
83
84 private import
85     std.thread,
86     std.stdio,
87     std.string,
88     std.gc,
89     st.tls;
90
91 //Handle versions
92 version(D_InlineAsm_X86)
93 {
94     version(DigitalMars)
95     {
96         version(Win32) version = SC_WIN_ASM;
97         version(linux) version = SC_LIN_ASM;
98     }
99    
100     //GDC uses a different calling conventions, need to reverse engineer them later
101 }
102
103
104 /// The default size of a StackContext's stack
105 const size_t DEFAULT_STACK_SIZE = 0x40000;
106
107 /// The minimum size of a StackContext's stack
108 const size_t MINIMUM_STACK_SIZE = 0x1000;
109
110 /// The state of a context object
111 enum CONTEXT_STATE
112 {
113     READY,      /// When a StackContext is in ready state, it may be run
114     RUNNING,    /// When a StackContext is running, it is currently in use, and cannot be run
115     DEAD,       /// When a StackContext is dead, it may no longer be run
116 }
117
118 /******************************************************
119  * A ContextException is generated whenever there is a
120  * problem in the StackContext system.  ContextExceptions
121  * can be triggered by running out of memory, or errors
122  * relating to doubly starting threads.
123  ******************************************************/
124 public class ContextException : Exception
125 {
126     this(char[] msg) { super( msg ); }
127    
128     this(StackContext context, char[] msg)
129     {
130         if(context is null)
131         {
132             debug (StackContext) writefln("Generated an exception: %s", msg);
133             super(msg);
134         }
135         else
136         {
137             debug (StackContext) writefln("%s generated an exception: %s", context.toString, msg);
138             super(format("Context %s: %s", context.toString, msg));
139         }
140     }
141 }
142
143
144
145 /******************************************************
146  * A ContextError is generated whenever something
147  * horrible and unrecoverable happens.  Like writing out
148  * of the stack.
149  ******************************************************/
150 public class ContextError : Error
151 {
152     this(char[] msg)
153     {
154         super(msg);
155     }
156 }
157
158
159
160
161 /******************************************************
162  * The StackContext is building block of the
163  * StackThread system. It allows the user to swap the
164  * stack of the running program.
165  *
166  * For most applications, there should be no need to use
167  * the StackContext, since the StackThreads are simpler.
168  * However, the StackContext can provide useful features
169  * for custom schedulers and coroutines.
170  *
171  * Any non running context may be restarted.  A restarted
172  * context starts execution from the beginning of its
173  * delegate.
174  *
175  * Contexts may be nested arbitrarily, ie Context A invokes
176  * Context B, such that when B yields A is resumed.
177  *
178  * Calling run on already running or dead context will
179  * result in an exception.
180  *
181  * If an exception is generated in a context and it is
182  * not caught, then it will be rethrown from the run
183  * method.  A program calling 'run' must be prepared
184  * to deal with any exceptions that might be thrown.  Once
185  * a context has thrown an exception like this, it dies
186  * and must be restarted before it may be run again.
187  *
188  * Example:
189  * <code><pre>
190  * // Here is a trivial example using contexts.
191  * // More sophisticated uses of contexts can produce
192  * // iterators, concurrent state machines and coroutines
193  * //
194  * void func1()
195  * {
196  *     writefln("Context 1 : Part 1");
197  *     StackContext.yield();
198  *     writefln("Context 1 : Part 2");
199  * }
200  * void func2()
201  * {
202  *     writefln("Context 2 : Part 1");
203  *     StackContext.yield();
204  *     writefln("Context 2 : Part 2");
205  * }
206  * //Create the contexts
207  * StackContext ctx1 = new StackContext(&func1);
208  * StackContext ctx2 = new StackContext(&func2);
209  *
210  * //Run the contexts
211  * ctx1.run();     // Prints "Context 1 : Part 1"
212  * ctx2.run();     // Prints "Context 2 : Part 1"
213  * ctx1.run();     // Prints "Context 1 : Part 2"
214  * ctx2.run();     // Prints "Context 2 : Part 2"
215  *
216  * //Here is a more sophisticated example using
217  * //exceptions
218  * //
219  * void func3()
220  * {
221  *      writefln("Going to throw");
222  *      StackContext.yield();
223  *      throw new Exception("Test Exception");
224  * }
225  * //Create the context
226  * StackContext ctx3 = new StackContext(&func3);
227  *
228  * //Now run the context
229  * try
230  * {
231  *      ctx3.run();     // Prints "Going to throw"
232  *      ctx3.run();     // Throws an exception
233  *      writefln("Bla");// Never gets here
234  * }
235  * catch(Exception e)
236  * {
237  *      e.print();      // Prints "Test Exception"
238  *      //We can't run ctx3 anymore unless we restart it
239  *      ctx3.restart();
240  *      ctx3.run();     // Prints "Going to throw"
241  * }
242  *
243  * //A final example illustrating context nesting
244  * //
245  * StackContext A, B;
246  *
247  * void funcA()
248  * {
249  *     writefln("A : Part 1");
250  *     B.run();
251  *     writefln("A : Part 2");
252  *     StackContext.yield();
253  *     writefln("A : Part 3");
254  * }
255  * void funcB()
256  * {
257  *      writefln("B : Part 1");
258  *      StackContext.yield();
259  *      writefln("B : Part 2");
260  * }
261  * A = new StackContext(&funcA);
262  * B = new StackContext(&funcB);
263  *
264  * //We first run A
265  * A.run();     //Prints "A : Part 1"
266  *              //       "B : Part 1"
267  *              //       "A : Part 2"
268  *              //
269  * //Now we run B
270  * B.run();     //Prints "B : Part 2"
271  *              //
272  * //Now we finish A
273  * A.run();     //Prints "A : Part 3"
274  *
275  * </pre></code>
276  *
277  ******************************************************/
278 public final class StackContext
279 {
280     /**
281      * Create a StackContext with the given stack size,
282      * using a delegate.
283      *
284      * Params:
285      *  fn = The delegate we will be running.
286      *  stack_size = The size of the stack for this thread
287      *  in bytes.  Note, Must be greater than the minimum
288      *  stack size.
289      *
290      * Throws:
291      *  A ContextException if there is insufficient memory
292      *  for the stack.
293      */
294     public this(void delegate() fn, size_t stack_size = DEFAULT_STACK_SIZE)
295     in
296     {
297         assert(fn !is null);
298         assert(stack_size >= MINIMUM_STACK_SIZE);
299     }
300     body
301     {
302         //Initalize the delegate
303         proc = fn;
304        
305         //Set up the stack
306         setupStack(stack_size);
307        
308         debug (StackContext) writefln("Created %s", this.toString);
309     }
310    
311     /**
312      * Create a StackContext with the given stack size,
313      * using a function pointer.
314      *
315      * Params:
316      *  fn = The function pointer we are using
317      *  stack_size = The size of the stack for this thread
318      *  in bytes.  Note, Must be greater than the minimum
319      *  stack size.
320      *
321      * Throws:
322      *  A ContextException if there is insufficient memory
323      *  for the stack.
324      */
325     public this(void function() fn, size_t stack_size = DEFAULT_STACK_SIZE)
326     in
327     {
328         assert(fn !is null);
329         assert(stack_size >= MINIMUM_STACK_SIZE);
330     }
331     body
332     {
333         //Caste fn to delegate
334         f_proc = fn;
335         proc = &to_dg;
336        
337         setupStack(stack_size);
338        
339         debug (StackContext) writefln("Created %s", this.toString);
340     }
341    
342    
343     /**
344      * Release the stack context.  Note that since stack
345      * contexts are NOT GARBAGE COLLECTED, they must be
346      * explicitly freed.  This usually taken care of when
347      * the user creates the StackContext implicitly via
348      * StackThreads, but in the case of a Context, it must
349      * be handled on a per case basis.
350      *
351      * Throws:
352      *  A ContextError if the stack is corrupted.
353      */
354     ~this()
355     in
356     {
357         assert(state != CONTEXT_STATE.RUNNING);
358         assert(current_context.val !is this);
359     }
360     body
361     {
362         debug (StackContext) writefln("Deleting %s", this.toString);
363        
364         //Delete the stack if we are not dead
365         deleteStack();
366     }
367    
368     /**
369      * Run the context once.  This causes the function to
370      * run until it invokes the yield method in this
371      * context, at which point control returns to the place
372      * where code invoked the program.
373      *
374      * Throws:
375      *  A ContextException if the context is not READY.
376      *
377      *  Any exceptions generated in the context are
378      *  bubbled up through this method.
379      */
380     public final void run()
381     {
382         debug (StackContext) writefln("Running %s", this.toString);
383        
384         //We must be ready to run
385         assert(state == CONTEXT_STATE.READY,
386             "Context is not in a runnable state");
387        
388         //Save the old context
389         StackContext tmp = current_context.val;
390        
391         version(LEAK_FIX)
392         {
393             //Mark GC info
394             debug (LogGC) writefln("Adding range: %8x-%8x", &tmp, getStackBottom());
395             addRange(cast(void*)&tmp, getStackBottom());
396         }
397        
398         //Set new context
399         current_context.val = this;
400         ctx.switchIn();
401         current_context.val = tmp;
402        
403         assert(state != CONTEXT_STATE.RUNNING);
404        
405         version(LEAK_FIX)
406         {
407             //Clear GC info
408             debug (LogGC) writefln("Removing range: %8x", &tmp);
409             removeRange(cast(void*)&tmp);
410            
411            
412             //If we are dead, we need to release the GC
413             if(state == CONTEXT_STATE.DEAD &&
414                 gc_start !is null)
415             {
416                 debug (LogGC) writefln("Removing range: %8x", gc_start);
417                 removeRange(gc_start);
418                 gc_start = null;
419             }
420         }
421        
422         // Pass any exceptions generated up the stack
423         if(last_exception !is null)
424         {
425             debug (StackContext) writefln("%s generated an exception: %s", this.toString, last_exception.toString);
426            
427             //Clear the exception
428             Object tmpo = last_exception;
429             last_exception = null;
430            
431             //Pass it up
432             throw tmpo;
433         }
434        
435         debug (StackContext) writefln("Done running context: %s", this.toString);
436     }
437    
438    
439     /**
440      * Returns control of the application to the routine
441      * which invoked the StackContext.  At which point,
442      * the application runs.
443      *
444      * Throws:
445      *  A ContextException when there is no currently
446      *  running context.
447      */
448     public final static void yield()
449     {
450         StackContext cur_ctx = current_context.val;
451        
452         //Make sure we are actually running
453         assert(cur_ctx !is null,
454             "Tried to yield without any running contexts.");
455        
456         debug (StackContext) writefln("Yielding %s", cur_ctx.toString);
457        
458         assert(cur_ctx.running);
459        
460         //Leave the current context
461         cur_ctx.state = CONTEXT_STATE.READY;
462         StackContext tmp = cur_ctx;
463        
464         version(LEAK_FIX)
465         {
466             //Save the GC range
467             cur_ctx.gc_start = cast(void*)&tmp;
468             debug (LogGC) writefln("Adding range: %8x-%8x",
469                 cur_ctx.gc_start, cur_ctx.ctx.stack_top);
470             addRange(cur_ctx.gc_start, cur_ctx.ctx.stack_top);
471         }
472        
473         //Swap
474         cur_ctx.ctx.switchOut();
475        
476         version(LEAK_FIX)
477         {
478             StackContext t_ctx = current_context.val;
479            
480             //Remove the GC range
481             debug (LogGC) writefln("Removing range: %8x",
482                 t_ctx.gc_start);
483             assert(t_ctx.gc_start !is null);
484             removeRange(t_ctx.gc_start);
485             t_ctx.gc_start = null;
486         }
487        
488         //Return
489         current_context.val = tmp;
490         tmp.state = CONTEXT_STATE.RUNNING;
491        
492         debug (StackContext) writefln("Resuming context: %s", tmp.toString);
493     }
494    
495     /**
496      * Throws an exception and yields.  The exception
497      * will propagate out of the run method, while the
498      * context will remain alive and functioning.
499      * The context may be resumed after the exception has
500      * been thrown.
501      *
502      * Params:
503      *  t = The exception object we will propagate.
504      */
505     public final static void throwYield(Object t)
506     {
507         current_context.val.last_exception = t;
508         yield();
509     }
510    
511     /**
512      * Resets the context to its original state.
513      *
514      * Throws:
515      *  A ContextException if the context is running.
516      */
517     public final void restart()
518     {
519         debug (StackContext) writefln("Restarting %s", this.toString);
520        
521         assert(state != CONTEXT_STATE.RUNNING,
522             "Cannot restart a context while it is running");
523        
524         //Reset the context
525         restartStack();
526     }
527    
528     /**
529      * Recycles the context by restarting it with a new delegate. This
530      * can save resources by allowing a program to reuse previously
531      * allocated contexts.
532      *
533      * Params:
534      *  dg = The delegate which we will be running.
535      */
536     public final void recycle(void delegate() dg)
537     {
538         debug (StackContext) writefln("Recycling %s", this.toString);
539        
540         assert(state != CONTEXT_STATE.RUNNING,
541             "Cannot recycle a context while it is running");
542        
543         //Set the delegate and restart
544         proc = dg;
545         restartStack();
546     }
547    
548     /**
549      * Immediately sets the context state to dead. This
550      * can be used as an alternative to deleting the
551      * context since it releases any GC references, and
552      * may be easily reallocated.
553      *
554      * Throws:
555      *  A ContextException if the context is not READY.
556      */
557     public final void kill()
558     {
559         assert(state != CONTEXT_STATE.RUNNING,
560             "Cannot kill a context while it is running.");
561        
562        
563         version(LEAK_FIX)
564         {
565             if(state == CONTEXT_STATE.DEAD)
566             {
567                 return;
568             }
569            
570             //Clear the GC ranges if necessary
571             if(gc_start !is null)
572             {
573                 debug (LogGC) writefln("Removing range: %8x", gc_start);
574                 removeRange(gc_start);
575                 gc_start = null;
576             }
577         }
578        
579         state = CONTEXT_STATE.DEAD;
580     }
581    
582     /**
583      * Convert the context into a human readable string,
584      * for debugging purposes.
585      *
586      * Returns: A string describing the context.
587      */
588     public final char[] toString()
589     {
590         static char[][] state_names =
591         [
592             "RDY",
593             "RUN",
594             "XXX",
595         ];
596        
597         //horrid hack for getting the address of a delegate
598         union hack
599         {
600             struct dele
601             {
602                 void * frame;
603                 void * fptr;
604             }
605            
606             dele d;
607             void delegate () dg;
608         }
609         hack h;
610         if(f_proc !is null)
611             h.d.fptr = cast(void*)f_proc;
612         else
613             h.dg = proc;
614        
615         return format(
616             "Context[sp:%8x,st:%s,fn:%8x]",
617             ctx.stack_pointer,
618             state_names[cast(int)state],
619             h.d.fptr);
620     }
621    
622     /**
623      * Returns: The state of this stack context.
624      */
625     public CONTEXT_STATE getState()
626     {
627         return state;
628     }
629    
630     /**
631      * Returns: True if the context can be run.
632      */
633     public bool ready()
634     {
635         return state == CONTEXT_STATE.READY;
636     }
637    
638     /**
639      * Returns: True if the context is currently running
640      */
641     public bool running()
642     {
643         return state == CONTEXT_STATE.RUNNING;
644     }
645    
646     /**
647      * Returns: True if the context is currenctly dead
648      */
649     public bool dead()
650     {
651         return state == CONTEXT_STATE.DEAD;
652     }
653    
654     /**
655      * Returns: The currently running stack context.
656      *  null if no context is currently running.
657      */
658     public static StackContext getRunning()
659     {
660         return current_context.val;
661     }
662    
663     invariant
664     {
665        
666         switch(state)
667         {
668             case CONTEXT_STATE.RUNNING:
669                 //Make sure context is running
670                 //assert(ctx.old_stack_pointer !is null);
671                 assert(current_context.val !is null);
672            
673             case CONTEXT_STATE.READY:
674                 //Make sure state is ready
675                 assert(ctx.stack_bottom !is null);
676                 assert(ctx.stack_top !is null);
677                 assert(ctx.stack_top >= ctx.stack_bottom);
678                 assert(ctx.stack_top - ctx.stack_bottom >= MINIMUM_STACK_SIZE);
679                 assert(ctx.stack_pointer !is null);
680                 assert(ctx.stack_pointer >= ctx.stack_bottom);
681                 assert(ctx.stack_pointer <= ctx.stack_top);
682                 assert(proc !is null);
683             break;
684            
685             case CONTEXT_STATE.DEAD:
686                 //Make sure context is dead
687                 //assert(gc_start is null);
688             break;
689            
690             default: assert(false);
691         }
692     }
693        
694     version(LEAK_FIX)
695     {
696         // Start of GC range
697         private void * gc_start = null;
698     }
699    
700     // The system context
701     private SysContext ctx;
702
703     // Context state
704     private CONTEXT_STATE state;
705    
706     // The last exception generated
707     private static Object last_exception = null;
708    
709 /*BEGIN TLS {*/
710        
711     // The currently running stack context
712     private static ThreadLocal!(StackContext) current_context = null;
713    
714 /*} END TLS*/
715    
716     // The procedure this context is running
717     private void delegate() proc = null;
718
719     // Used to convert a function pointer to a delegate
720     private void function() f_proc = null;
721     private void to_dg() { f_proc(); }
722    
723
724     /**
725      * Initialize the stack for the context.
726      */
727     private void setupStack(size_t stack_size)
728     {
729         //Initialize the stack
730         ctx.initStack(stack_size);
731        
732         //Initialize context state
733         state = CONTEXT_STATE.READY;
734        
735         version(LEAK_FIX)
736         {
737             assert(gc_start is null);
738             gc_start = null;
739         }
740         else
741         {
742             addRange(ctx.getStackStart, ctx.getStackEnd);
743         }
744     }
745    
746     /**
747      * Restart the context.
748      */
749     private void restartStack()
750     {
751         version(LEAK_FIX)
752         {
753             //Clear the GC ranges if necessary
754             if(gc_start !is null)
755             {
756                 debug (LogGC) writefln("Removing range: %8x", gc_start);
757                 removeRange(gc_start);
758                 gc_start = null;
759             }
760         }
761        
762         ctx.resetStack();
763         state = CONTEXT_STATE.READY;
764     }
765    
766     /**
767      * Delete the stack
768      */
769     private void deleteStack()
770     {
771         version(LEAK_FIX)
772         {
773             //Clear the GC ranges if necessary
774             if(gc_start !is null)
775             {
776                 debug (LogGC) writefln("Removing range: %8x", gc_start);
777                 removeRange(gc_start);
778                 gc_start = null;
779             }
780         }
781         else
782         {
783             removeRange(ctx.getStackStart);
784         }
785        
786         // Clear state
787         state = CONTEXT_STATE.DEAD;
788         proc = null;
789         f_proc = null;
790        
791         // Kill the stack
792         ctx.killStack();
793     }
794    
795     /**
796      * Run the context
797      */
798     private static extern(C) void startContext()
799     in
800     {
801         assert(current_context.val !is null);
802         version</