Replicate .NET behavior for exceptions in the finalizer thread.
authoralexrp <xtzgzorex@gmail.com>
Wed, 25 Apr 2012 13:57:40 +0000 (15:57 +0200)
committeralexrp <xtzgzorex@gmail.com>
Wed, 25 Apr 2012 17:05:34 +0000 (19:05 +0200)
On .NET, when an exception occurs in the finalizer thread, it is
printed, the AppDomain.UnhandledException event is raised, and
the runtime is aborted (exact same thing as what happens when an
exception is thrown in a thread pool thread).

We now do the same.

Note that this is a breaking change; previously, we just silently
swallowed exceptions that occurred in the finalizer thread. Given
this, a test had to be adjusted (and renamed). An extra test has
been added to verify that the UnhandledException event is raised
correctly.

mono/metadata/gc.c
mono/metadata/threadpool-internals.h
mono/metadata/threadpool.c
mono/tests/Makefile.am
mono/tests/finalizer-exception.cs
mono/tests/finalizer-thread.cs [new file with mode: 0644]

index 1a89248dd0f9b6ba7086b9297fac5c49e67f0d25..9463c7ebff182fa538f264f407fbc0bb6d221f8e 100644 (file)
@@ -24,6 +24,7 @@
 #include <mono/metadata/metadata-internals.h>
 #include <mono/metadata/mono-mlist.h>
 #include <mono/metadata/threadpool.h>
+#include <mono/metadata/threadpool-internals.h>
 #include <mono/metadata/threads-types.h>
 #include <mono/utils/mono-logger-internal.h>
 #include <mono/metadata/gc-internal.h>
@@ -225,9 +226,8 @@ mono_gc_run_finalize (void *obj, void *data)
 
        runtime_invoke (o, NULL, &exc, NULL);
 
-       if (exc) {
-               /* fixme: do something useful */
-       }
+       if (exc)
+               mono_internal_thread_unhandled_exception (exc);
 
        mono_domain_set_internal (caller_domain);
 }
index 10e9330b6fbac9578f11f77adeb1c5a34071e98f..9d73ccdeb305ec3d353259fd537bf1e2f451ec7c 100644 (file)
@@ -3,5 +3,6 @@
 
 void mono_thread_pool_remove_socket (int sock) MONO_INTERNAL;
 gboolean mono_thread_pool_is_queue_array (MonoArray *o) MONO_INTERNAL;
+void mono_internal_thread_unhandled_exception (MonoObject* exc) MONO_INTERNAL;
 
 #endif
index 961a8fc27d1773af8b5defe36ea2f2ea691913c4..089df523fa97d9ee2ba9d3d279bb989eec10fcb5 100644 (file)
@@ -1440,20 +1440,8 @@ async_invoke_thread (gpointer data)
                                        exc = mono_async_invoke (tp, ar);
                                        if (tp_item_end_func)
                                                tp_item_end_func (tp_item_user_data);
-                                       if (exc && mono_runtime_unhandled_exception_policy_get () == MONO_UNHANDLED_POLICY_CURRENT) {
-                                               gboolean unloaded;
-                                               MonoClass *klass;
-
-                                               klass = exc->vtable->klass;
-                                               unloaded = is_appdomainunloaded_exception (exc->vtable->domain, klass);
-                                               if (!unloaded && klass != mono_defaults.threadabortexception_class) {
-                                                       mono_unhandled_exception (exc);
-                                                       if (mono_environment_exitcode_get () == 1)
-                                                               exit (255);
-                                               }
-                                               if (klass == mono_defaults.threadabortexception_class)
-                                                       mono_thread_internal_reset_abort (thread);
-                                       }
+                                       if (exc)
+                                               mono_internal_thread_unhandled_exception (exc);
                                        if (is_socket && tp->is_io) {
                                                MonoSocketAsyncResult *state = (MonoSocketAsyncResult *) data;
 
@@ -1654,3 +1642,21 @@ mono_install_threadpool_item_hooks (MonoThreadPoolItemFunc begin_func, MonoThrea
        tp_item_user_data = user_data;
 }
 
+void
+mono_internal_thread_unhandled_exception (MonoObject* exc)
+{
+       if (mono_runtime_unhandled_exception_policy_get () == MONO_UNHANDLED_POLICY_CURRENT) {
+               gboolean unloaded;
+               MonoClass *klass;
+
+               klass = exc->vtable->klass;
+               unloaded = is_appdomainunloaded_exception (exc->vtable->domain, klass);
+               if (!unloaded && klass != mono_defaults.threadabortexception_class) {
+                       mono_unhandled_exception (exc);
+                       if (mono_environment_exitcode_get () == 1)
+                               exit (255);
+               }
+               if (klass == mono_defaults.threadabortexception_class)
+                mono_thread_internal_reset_abort (mono_thread_internal_current ());
+       }
+}
index 59c9c2e963808ada268715bdd37015229e8e7a81..8731eba7f8e23804eca690f3193ad06d849a9ec8 100644 (file)
@@ -238,6 +238,7 @@ BASE_TEST_CS_SRC=           \
        finalizer-abort.cs      \
        finalizer-exception.cs  \
        finalizer-exit.cs       \
+       finalizer-thread.cs     \
        main-exit.cs    \
        main-returns-abort-resetabort.cs        \
        main-returns-background-abort-resetabort.cs     \
index 595bdebe3af3d5e1cf372e0ab1434bb9b00e1869..d11e1696f1487540efebaa76a63c95f769e0e5c1 100644 (file)
@@ -1,39 +1,24 @@
-
-using System; 
-using System.Collections; 
+using System;
 using System.Threading;
 
-public class foo  { 
-       public static LocalDataStoreSlot dataslot = Thread.AllocateDataSlot();
-       public static int final_count=0;
+public class FinalizerException {
+       ~FinalizerException () {
+               throw new Exception ();
+       }
 
-       ~foo() { 
-               // Demonstrate that this is still the same thread
-               string ID=(string)Thread.GetData(dataslot);
-               if(ID==null) {
-                       Console.WriteLine("Set ID: foo");
-                       Thread.SetData(dataslot, "foo");
-               }
+       public static int Main () { 
+               AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
+                       Console.WriteLine ("caught");
+                       Environment.Exit (0);
+               };
 
-               // Don't run forever
-               if(final_count++>10) {
-                       Environment.Exit(0);
-               }
+               new FinalizerException ();
 
-               Console.WriteLine("finalizer thread ID: {0}", (string)Thread.GetData(dataslot));
-               throw new SystemException("wibble");
-       } 
+               GC.Collect ();
+               GC.WaitForPendingFinalizers ();
 
-       public static int Main() { 
-               ArrayList list = new ArrayList (); 
-               Thread.SetData(dataslot, "ID is wibble");
-               Environment.ExitCode = 2;
-               while(true) { 
-                       foo instance = new foo(); 
-                       list.Add (new WeakReference(instance)); 
-                       Thread.Sleep (0);
-               }
-               return 1;
-       } 
-} 
+               Thread.Sleep (Timeout.Infinite); // infinite wait so we don't race against the unhandled exception callback
 
+               return 2;
+       }
+}
diff --git a/mono/tests/finalizer-thread.cs b/mono/tests/finalizer-thread.cs
new file mode 100644 (file)
index 0000000..afcbaf1
--- /dev/null
@@ -0,0 +1,41 @@
+
+using System; 
+using System.Collections; 
+using System.Threading;
+
+public class foo  { 
+       public static LocalDataStoreSlot dataslot = Thread.AllocateDataSlot();
+       public static int final_count=0;
+
+       ~foo() { 
+               // Demonstrate that this is still the same thread
+               string ID=(string)Thread.GetData(dataslot);
+               if(ID==null) {
+                       Console.WriteLine("Set ID: foo");
+                       Thread.SetData(dataslot, "foo");
+               }
+
+               // Don't run forever
+               if(final_count++>10) {
+                       Environment.Exit(0);
+               }
+
+               Console.WriteLine("finalizer thread ID: {0}", (string)Thread.GetData(dataslot));
+
+               if ((string)Thread.GetData(dataslot) != "foo")
+                       throw new Exception ();
+       } 
+
+       public static int Main() { 
+               ArrayList list = new ArrayList (); 
+               Thread.SetData(dataslot, "ID is wibble");
+               Environment.ExitCode = 2;
+               while(true) { 
+                       foo instance = new foo(); 
+                       list.Add (new WeakReference(instance)); 
+                       Thread.Sleep (0);
+               }
+               return 1;
+       } 
+} 
+