| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- Threading in Mono
- =================
- 0. Terminology
- --------------
- "Main thread" - The initial OS-native thread that the
- application started with.
- "Helper thread" - A native thread created internally
- by the runtime, such as the finalizer thread, or an
- asynchronous delegate invocation thread. These
- threads can run managed code.
-
- "Primary CLR thread" - The native thread that called
- the Main() method when executing an assembly.
- "Secondary CLR thread" - A native thread created by a
- program that instantiates a System.Threading.Thread object
- and calls its Start() method.
- 1. Thread exit behaviour in the standalone mono runtime
- -------------------------------------------------------
- The correct behaviour of the runtime should be:
- a) If Main() returns, the runtime should wait for all
- foreground secondary CLR threads to finish. The
- wording in the class documentation states: "Once all
- foreground threads belonging to a process have
- terminated, the common language runtime ends the
- process by invoking Abort on any background threads
- that are still alive." Testing seems to indicate that
- the background thread can't cancel the Abort by
- catching the ThreadAbortException and calling
- ResetAbort here. Indeed, not even the finally block
- seems to be executed.
- b) if any of the primary CLR thread, a secondary CLR
- thread or a helper thread calls
- System.Environment.Exit(), the application should
- terminate immediately without waiting for foreground
- primary or secondary CLR threads to finish.
- c) if the primary CLR thread throws an uncaught
- exception, the application should terminate
- immediately without waiting for secondary CLR threads
- to finish. This might be implemented internally by
- pretending that all the running secondary CLR threads
- are background threads.
- d) if a secondary CLR thread throws an uncaught
- exception that thread should terminate and all other
- threads should continue to execute.
- e) if a helper thread throws an uncaught exception and
- that thread happens to be the GC finalizer thread,
- testing seems to indicate that the exception stack
- trace is displayed as normal, and the exception is
- then ignored (as though there is a try {} catch{}
- around all finalizers that just prints the stack
- trace.) Calling Abort() on the GC finalizer thread
- also does not cause it to exit: it behaves as though
- the ThreadAbortException is caught and ResetAbort is
- called. Asynchronous delegate helper threads should
- behave as secondary CLR threads, but uncaught
- exceptions should be rethrown on the thread that calls
- EndInvoke().
- The difficulties happen with cases b) and c):
- The current implementation of
- System.Environment.Exit() calls exit(2) directly,
- which is rather unfriendly: it prevents any runtime
- cleanup, statistics gathering, etc. and is pretty
- obnoxious to embedded code.
- The current exception handling code calls ExitThread()
- (emulated with pthread_exit() in the io-layer) if an
- exception is not caught.
- When called from the main thread, both POSIX
- pthread_exit() and w32 ExitThread() block if there are
- other threads still running (in the w32 case, if there
- are other foreground threads still running; threads
- can set as background.) If the main thread is also
- the primary CLR thread, then the application will
- block until all other threads (including helper
- threads) terminate. Some helper threads will not
- terminate until specifically told to by the runtime:
- for example, the GC finalizer thread needs to run
- until all of the primary and secondary CLR threads
- have finished.
- Also, if the main thread is also the primary CLR
- thread, the runtime loses the opportunity to do any
- cleaning up. Adding a special case to call exit(2)
- instead of ExitThread() in the primary CLR thread
- suffers from the same problems as
- System.Environment.Exit() calling exit(2).
- The simple solution is to run the primary CLR thread
- in a new native thread, leaving the main thread free
- for housekeeping duties. There still needs to be some
- special handling for the case where the primary CLR
- thread fails to catch an exception: the secondary CLR
- threads then need to be terminated.
- When the primary and secondary CLR threads have all
- terminated, the helper threads can be killed off and
- the runtime can clean itself up and exit.
- 2. Thread initialisation
- ------------------------
- Threads have to undergo some initialisation before
- managed code can be executed. A
- System.Threading.Thread object must be created, and
- the thread details need to be stored so that the
- threads can be managed later. The JIT needs to record
- the last managed frame stack pointer in a TLS slot,
- and the current Thread object is also recorded.
- New threads created by managed calls to
- System.Threading.Thread methods will have all needed
- initialisation performed. Threads created by the
- runtime with calls to mono_thread_create() will too.
- Existing threads can be passed to the runtime; these
- must call mono_thread_attach() before any CLR code can
- be executed on that thread.
- 3. Constraints on embedding the Mono runtime
- --------------------------------------------
- The discussion above concerning application behaviour
- in the event of threads terminating, whether by
- returning from the start function, throwing uncaught
- exceptions or by calling System.Environment.Exit(),
- only really applies to the standalone Mono runtime.
- An embedding application should specify what behaviour
- is required when, for example,
- System.Environment.Exit() is called. The application
- is also responsible for its own thread management, and
- it should be prepared for any of the primary CLR
- thread or secondary CLR threads to terminate at any
- time. The application should also take into account
- that the runtime will create helper threads as needed,
- as this may cause pthread_exit() or ExitThread() to
- block indefinitely, as noted above.
|