
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.
