You've already forked linux-packaging-mono
							
							
		
			
				
	
	
		
			157 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			157 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 
 | |
| 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.
 |