Hello,

This patch adds support for stack|context propagation between threads in
the following cases:

        * Thread.Start;
        * delegates (BeginInvoke|EndInvoke);
        * ThreadPool.[Unsafe]QueueUserWorkItem;
        
Several tests are available under
/mono/mono/tests/cas/threads/


Note that the propagation stuff is much updated in CLR|FX 2.0, i.e. it
doesn't _only_ apply to security anymore. This is why the runtime patch
looks for either (a) the security manager being active or (b) a corlib
version > 2.x to enable the capture of the execution context of the
thread (but in the later case the CAS security stack propagation is
still only done if the security manager is active).

The ThreadPool.UnsafeQueueUserWorkItem is "unsafe" in two ways ;-) First
it unsafe framework-wise because (a) it doesn't propagate the stack (so
it has some security prerequisites) and (b) the current implementation
doesn't make it 100% sure that the stack won't be propagated (if the
other rules applies). We're missing a "safe" way to tell the runtime if
we want a safe or unsafe invocation.


Comments welcome!


P.S. A previous patch was sent to mono-winforms-list, earlier this week,
for dealing with Control.BeginInvoke. The (updated) code is already in
SVN.
http://lists.ximian.com/archives/public/mono-winforms-list/2005-May/001635.html
-- 
Sebastien Pouliot  <[EMAIL PROTECTED]>
blog: http://pages.infinit.net/ctech/poupou.html
Index: metadata/domain.c
===================================================================
--- metadata/domain.c	(revision 44492)
+++ metadata/domain.c	(working copy)
@@ -615,6 +615,9 @@
 	mono_defaults.runtimesecurityframe_class = mono_class_from_name (
 	        mono_defaults.corlib, "System.Security", "RuntimeSecurityFrame");
 
+	mono_defaults.executioncontext_class = mono_class_from_name (
+	        mono_defaults.corlib, "System.Threading", "ExecutionContext");
+
 	domain->friendly_name = g_path_get_basename (filename);
 
 	return domain;
Index: metadata/security-manager.c
===================================================================
--- metadata/security-manager.c	(revision 44492)
+++ metadata/security-manager.c	(working copy)
@@ -189,6 +189,34 @@
 	return TRUE;
 }
 
+/*
+ * Context propagation is required when:
+ * (a) the security manager is active (1.x and later)
+ * (b) other contexts needs to be propagated (2.x and later)
+ *
+ * returns NULL if no context propagation is required, else the returns the
+ * MonoMethod to call to Capture the ExecutionContext.
+ */
+MonoMethod*
+mono_get_context_capture_method (void)
+{
+	static MonoMethod *method = NULL;
+
+	if (!mono_security_manager_activated) {
+		if (mono_image_get_assembly (mono_defaults.corlib)->aname.major < 2)
+			return NULL;
+	}
+
+	/* older corlib revisions won't have the class (nor the method) */
+	if (mono_defaults.executioncontext_class && !method) {
+		mono_class_init (mono_defaults.executioncontext_class);
+		method = mono_class_get_method_from_name (mono_defaults.executioncontext_class, "Capture", 0);
+	}
+
+	return method;
+}
+
+
 /* System.Security icalls */
 
 MonoBoolean
Index: metadata/security-manager.h
===================================================================
--- metadata/security-manager.h	(revision 44492)
+++ metadata/security-manager.h	(working copy)
@@ -57,6 +57,7 @@
 gboolean mono_is_security_manager_active (void);
 MonoSecurityManager* mono_security_manager_get_methods (void);
 gboolean mono_is_ecma_key (const char *publickey, int size);
+MonoMethod* mono_get_context_capture_method (void);
 
 void mono_secman_inheritancedemand_class (MonoClass *klass, MonoClass *parent);
 void mono_secman_inheritancedemand_method (MonoMethod *override, MonoMethod *base);
Index: metadata/ChangeLog
===================================================================
--- metadata/ChangeLog	(revision 44492)
+++ metadata/ChangeLog	(working copy)
@@ -1,3 +1,19 @@
+2005-05-13  Sebastien Pouliot  <[EMAIL PROTECTED]>
+
+	* class-internals.h: Added executioncontext_class field to 
+	MonoDefaults structure.
+	* domain.c: Cache System.Threading.ExecutionContext class in 
+	mono_defaults.
+	* object.c: Capture the ExecutionContext for asynchroneous calls in
+	 mono_async_result_new.
+	* object-internals.h: Added execution_context and original_context 
+	fields to MonoAsyncResult. Added execution_context to MonoThread.
+	* security-manager.c|.h: Added mono_get_context_capture_method to 
+	return the capture method (if required by the security manager or by
+	the framework version used).
+	* threadpool.c: Apply capture (if present) ExecutionContext in 
+	mono_async_invoke and revert to original context after it completes.
+
 2005-05-12  Zoltan Varga  <[EMAIL PROTECTED]>
 
 	* marshal.c (emit_marshal_custom): Add some error checking and call the
Index: metadata/threadpool.c
===================================================================
--- metadata/threadpool.c	(revision 44492)
+++ metadata/threadpool.c	(working copy)
@@ -887,7 +887,18 @@
 mono_async_invoke (MonoAsyncResult *ares)
 {
 	ASyncCall *ac = (ASyncCall *)ares->data;
+	MonoThread *thread = NULL;
+	MonoObject *original_context = NULL;
 
+	if (ares->execution_context) {
+		/* use captured ExecutionContext (if available) */
+		thread = mono_thread_current ();
+		ares->original_context = thread->execution_context;
+		thread->execution_context = ares->execution_context;
+	} else {
+		ares->original_context = NULL;
+	}
+
 	ac->msg->exc = NULL;
 	ac->res = mono_message_invoke (ares->async_delegate, ac->msg, 
 				       &ac->msg->exc, &ac->out_args);
@@ -903,6 +914,12 @@
 			ac->msg->exc = exc;
 	}
 
+	/* restore original thread execution context if flow isn't suppressed, i.e. non null */
+	if (ares->original_context) {
+		thread->execution_context = ares->original_context;
+		ares->original_context = NULL;
+	}
+
 	/* notify listeners */
 	mono_monitor_enter ((MonoObject *) ares);
 	if (ares->handle != NULL) {
Index: metadata/object.c
===================================================================
--- metadata/object.c	(revision 44492)
+++ metadata/object.c	(working copy)
@@ -3160,9 +3160,14 @@
 MonoAsyncResult *
 mono_async_result_new (MonoDomain *domain, HANDLE handle, MonoObject *state, gpointer data)
 {
-	MonoAsyncResult *res;
+	MonoAsyncResult *res = (MonoAsyncResult *)mono_object_new (domain, mono_defaults.asyncresult_class);
+	MonoMethod *method = mono_get_context_capture_method ();
 
-	res = (MonoAsyncResult *)mono_object_new (domain, mono_defaults.asyncresult_class);
+	/* we must capture the execution context from the original thread */
+	if (method) {
+		res->execution_context = mono_runtime_invoke (method, NULL, NULL, NULL);
+		/* note: result may be null if the flow is suppressed */
+	}
 
 	res->data = data;
 	res->async_state = state;
Index: metadata/class-internals.h
===================================================================
--- metadata/class-internals.h	(revision 44492)
+++ metadata/class-internals.h	(working copy)
@@ -567,6 +567,7 @@
 	MonoClass *monitor_class;
 	MonoClass *iremotingtypeinfo_class;
 	MonoClass *runtimesecurityframe_class;
+	MonoClass *executioncontext_class;
 } MonoDefaults;
 
 extern MonoDefaults mono_defaults;
Index: metadata/object-internals.h
===================================================================
--- metadata/object-internals.h	(revision 44492)
+++ metadata/object-internals.h	(working copy)
@@ -154,6 +154,8 @@
 	MonoBoolean  completed;
 	MonoBoolean  endinvoke_called;
 	MonoObject  *async_callback;
+	MonoObject  *execution_context;
+	MonoObject  *original_context;
 } MonoAsyncResult;
 
 typedef struct {
@@ -246,6 +248,7 @@
 	guint32 serialized_culture_info_len;
 	guint8* serialized_ui_culture_info;
 	guint32 serialized_ui_culture_info_len;
+	MonoObject *execution_context;
 	/* 
 	 * These fields are used to avoid having to increment corlib versions
 	 * when a new field is added to the unmanaged MonoThread structure.
@@ -257,7 +260,6 @@
 	gpointer unused5;
 	gpointer unused6;
 	gpointer unused7;
-	gpointer unused8;
 };
 
 typedef struct {
Index: System.Threading/ThreadPool.cs
===================================================================
--- System.Threading/ThreadPool.cs	(revision 44492)
+++ System.Threading/ThreadPool.cs	(working copy)
@@ -32,17 +32,21 @@
 using System.Collections;
 using System.Globalization;
 using System.Runtime.CompilerServices;
+using System.Runtime.Remoting.Messaging;
 using System.Security.Permissions;
 
 namespace System.Threading {
 
+#if NET_2_0
+	public static class ThreadPool {
+#else
 	public sealed class ThreadPool {
 
 		private ThreadPool ()
 		{
 			/* nothing to do */
 		}
-
+#endif
 		public static bool BindHandle (IntPtr osHandle)
 		{
 			return true;
@@ -132,10 +136,12 @@
 		[SecurityPermission (SecurityAction.Demand, ControlEvidence=true, ControlPolicy=true)]
 		public static bool UnsafeQueueUserWorkItem (WaitCallback callback, object state)
 		{
+			// no stack propagation here (that's why it's unsafe and requires extra security permissions)
 			IAsyncResult ar = callback.BeginInvoke (state, null, null);
-			if (ar == null)
-				return false;
-			return true;
+			AsyncResult result = (ar as AsyncResult);
+			if (result != null)
+				result.Unsafe ();
+			return (ar != null);
 		}
 		
 		[MonoTODO]
Index: System.Threading/Thread.cs
===================================================================
--- System.Threading/Thread.cs	(revision 44492)
+++ System.Threading/Thread.cs	(working copy)
@@ -86,6 +86,7 @@
 		private int serialized_culture_info_len;
 		private IntPtr serialized_ui_culture_info;
 		private int serialized_ui_culture_info_len;
+		private ExecutionContext _ec;
 		/* 
 		 * These fields are used to avoid having to increment corlib versions
 		 * when a new field is added to the unmanaged MonoThread structure.
@@ -97,7 +98,6 @@
 		private IntPtr unused5;
 		private IntPtr unused6;
 		private IntPtr unused7;
-		private IntPtr unused8;
 		#endregion
 
 		[ThreadStatic] 
@@ -109,8 +109,6 @@
 		
 		private IPrincipal _principal;
 
-		private ExecutionContext _ec;
-		
 		public static Context CurrentContext {
 			[SecurityPermission (SecurityAction.LinkDemand, Infrastructure=true)]
 			get {
@@ -629,6 +627,17 @@
 		}
 
 		public void Start() {
+			// propagate informations from the original thread to the new thread
+#if NET_2_0
+			if (!ExecutionContext.IsFlowSuppressed ())
+				_ec = ExecutionContext.Capture ();
+#else
+			// before 2.0 this was only used for security (mostly CAS) so we
+			// do this only if the security manager is active
+			if (SecurityManager.SecurityEnabled)
+				_ec = ExecutionContext.Capture ();
+#endif
+
 			// Thread_internal creates and starts the new thread, 
 			if (Thread_internal(threadstart) == (IntPtr) 0)
 				throw new SystemException ("Thread creation failed.");
Index: System.Runtime.Remoting.Messaging/AsyncResult.cs
===================================================================
--- System.Runtime.Remoting.Messaging/AsyncResult.cs	(revision 44492)
+++ System.Runtime.Remoting.Messaging/AsyncResult.cs	(working copy)
@@ -8,11 +8,8 @@
 //   Duncan Mak ([EMAIL PROTECTED])
 //
 // (C) 2001 Ximian, Inc.  http://www.ximian.com
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
 //
-
-//
-// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
-//
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
 // "Software"), to deal in the Software without restriction, including
@@ -49,6 +46,10 @@
 	bool completed;
 	bool endinvoke_called;
 	object async_callback;
+	ExecutionContext current;
+	ExecutionContext original;
+
+	// not part of MonoAsyncResult...
 	MonoMethodMessage call_message;
 	IMessageCtrl message_ctrl;
 	IMessage reply_message;
@@ -160,5 +161,11 @@
 		get { return call_message; }
 		set { call_message = value; }
 	}
+
+	internal void Unsafe ()
+	{
+		current = null;
+		original = null;
+	}
 }
 }

Reply via email to