8 "Main thread" - The initial OS-native thread that the
9 application started with.
11 "Helper thread" - A native thread created internally
12 by the runtime, such as the finalizer thread, or an
13 asynchronous delegate invocation thread. These
14 threads can run managed code.
16 "Primary CLR thread" - The native thread that called
17 the Main() method when executing an assembly.
19 "Secondary CLR thread" - A native thread created by a
20 program that instantiates a System.Threading.Thread object
21 and calls its Start() method.
23 1. Thread exit behaviour in the standalone mono runtime
24 -------------------------------------------------------
26 The correct behaviour of the runtime should be:
28 a) If Main() returns, the runtime should wait for all
29 foreground secondary CLR threads to finish. The
30 wording in the class documentation states: "Once all
31 foreground threads belonging to a process have
32 terminated, the common language runtime ends the
33 process by invoking Abort on any background threads
34 that are still alive." Testing seems to indicate that
35 the background thread can't cancel the Abort by
36 catching the ThreadAbortException and calling
37 ResetAbort here. Indeed, not even the finally block
40 b) if any of the primary CLR thread, a secondary CLR
41 thread or a helper thread calls
42 System.Environment.Exit(), the application should
43 terminate immediately without waiting for foreground
44 primary or secondary CLR threads to finish.
46 c) if the primary CLR thread throws an uncaught
47 exception, the application should terminate
48 immediately without waiting for secondary CLR threads
49 to finish. This might be implemented internally by
50 pretending that all the running secondary CLR threads
51 are background threads.
53 d) if a secondary CLR thread throws an uncaught
54 exception that thread should terminate and all other
55 threads should continue to execute.
57 e) if a helper thread throws an uncaught exception and
58 that thread happens to be the GC finalizer thread,
59 testing seems to indicate that the exception stack
60 trace is displayed as normal, and the exception is
61 then ignored (as though there is a try {} catch{}
62 around all finalizers that just prints the stack
63 trace.) Calling Abort() on the GC finalizer thread
64 also does not cause it to exit: it behaves as though
65 the ThreadAbortException is caught and ResetAbort is
66 called. Asynchronous delegate helper threads should
67 behave as secondary CLR threads, but uncaught
68 exceptions should be rethrown on the thread that calls
72 The difficulties happen with cases b) and c):
74 The current implementation of
75 System.Environment.Exit() calls exit(2) directly,
76 which is rather unfriendly: it prevents any runtime
77 cleanup, statistics gathering, etc. and is pretty
78 obnoxious to embedded code.
80 The current exception handling code calls ExitThread()
81 (emulated with pthread_exit() in the io-layer) if an
82 exception is not caught.
84 When called from the main thread, both POSIX
85 pthread_exit() and w32 ExitThread() block if there are
86 other threads still running (in the w32 case, if there
87 are other foreground threads still running; threads
88 can set as background.) If the main thread is also
89 the primary CLR thread, then the application will
90 block until all other threads (including helper
91 threads) terminate. Some helper threads will not
92 terminate until specifically told to by the runtime:
93 for example, the GC finalizer thread needs to run
94 until all of the primary and secondary CLR threads
97 Also, if the main thread is also the primary CLR
98 thread, the runtime loses the opportunity to do any
99 cleaning up. Adding a special case to call exit(2)
100 instead of ExitThread() in the primary CLR thread
101 suffers from the same problems as
102 System.Environment.Exit() calling exit(2).
105 The simple solution is to run the primary CLR thread
106 in a new native thread, leaving the main thread free
107 for housekeeping duties. There still needs to be some
108 special handling for the case where the primary CLR
109 thread fails to catch an exception: the secondary CLR
110 threads then need to be terminated.
112 When the primary and secondary CLR threads have all
113 terminated, the helper threads can be killed off and
114 the runtime can clean itself up and exit.
118 2. Thread initialisation
119 ------------------------
121 Threads have to undergo some initialisation before
122 managed code can be executed. A
123 System.Threading.Thread object must be created, and
124 the thread details need to be stored so that the
125 threads can be managed later. The JIT needs to record
126 the last managed frame stack pointer in a TLS slot,
127 and the current Thread object is also recorded.
129 New threads created by managed calls to
130 System.Threading.Thread methods will have all needed
131 initialisation performed. Threads created by the
132 runtime with calls to mono_thread_create() will too.
133 Existing threads can be passed to the runtime; these
134 must call mono_thread_attach() before any CLR code can
135 be executed on that thread.
138 3. Constraints on embedding the Mono runtime
139 --------------------------------------------
141 The discussion above concerning application behaviour
142 in the event of threads terminating, whether by
143 returning from the start function, throwing uncaught
144 exceptions or by calling System.Environment.Exit(),
145 only really applies to the standalone Mono runtime.
147 An embedding application should specify what behaviour
148 is required when, for example,
149 System.Environment.Exit() is called. The application
150 is also responsible for its own thread management, and
151 it should be prepared for any of the primary CLR
152 thread or secondary CLR threads to terminate at any
153 time. The application should also take into account
154 that the runtime will create helper threads as needed,
155 as this may cause pthread_exit() or ExitThread() to
156 block indefinitely, as noted above.