Merge pull request #2810 from kumpera/fix_hazard_free
[mono.git] / mono / metadata / gc.c
index e0f12d34b558cf3ad62cc353a2e91618c6377062..148bbedee6444a74666934c4a37d4b5377e69b2e 100644 (file)
@@ -6,6 +6,7 @@
  * Copyright 2002-2003 Ximian, Inc (http://www.ximian.com)
  * Copyright 2004-2009 Novell, Inc (http://www.novell.com)
  * Copyright 2012 Xamarin Inc (http://www.xamarin.com)
+ * Licensed under the MIT license. See LICENSE file in the project root for full license information.
  */
 
 #include <config.h>
@@ -39,6 +40,7 @@
 #include <mono/utils/mono-threads.h>
 #include <mono/utils/atomic.h>
 #include <mono/utils/mono-coop-semaphore.h>
+#include <mono/utils/hazard-pointer.h>
 
 #ifndef HOST_WIN32
 #include <pthread.h>
@@ -71,7 +73,7 @@ static MonoCoopCond exited_cond;
 
 static MonoInternalThread *gc_thread;
 
-static void object_register_finalizer (MonoObject *obj, void (*callback)(void *, void*));
+static void object_register_finalizer (MonoObject *obj, void (*callback)(void *, void*), MonoError *error);
 
 static void reference_queue_proccess_all (void);
 static void mono_reference_queue_cleanup (void);
@@ -108,6 +110,7 @@ static gboolean suspend_finalizers = FALSE;
 void
 mono_gc_run_finalize (void *obj, void *data)
 {
+       MonoError error;
        MonoObject *exc = NULL;
        MonoObject *o;
 #ifndef HAVE_SGEN_GC
@@ -161,7 +164,8 @@ mono_gc_run_finalize (void *obj, void *data)
 #endif
 
        /* make sure the finalizer is not called again if the object is resurrected */
-       object_register_finalizer ((MonoObject *)obj, NULL);
+       object_register_finalizer ((MonoObject *)obj, NULL, &error);
+       mono_error_assert_ok (&error); /* FIXME don't swallow the error */
 
        if (log_finalizers)
                g_log ("mono-gc-finalizers", G_LOG_LEVEL_MESSAGE, "<%s at %p> Registered finalizer as processed.", o->vtable->klass->name, o);
@@ -228,7 +232,7 @@ mono_gc_run_finalize (void *obj, void *data)
        }
 
        /* 
-        * To avoid the locking plus the other overhead of mono_runtime_invoke (),
+        * To avoid the locking plus the other overhead of mono_runtime_invoke_checked (),
         * create and precompile a wrapper which calls the finalize method using
         * a CALLVIRT.
         */
@@ -236,14 +240,15 @@ mono_gc_run_finalize (void *obj, void *data)
                g_log ("mono-gc-finalizers", G_LOG_LEVEL_MESSAGE, "<%s at %p> Compiling finalizer.", o->vtable->klass->name, o);
 
        if (!domain->finalize_runtime_invoke) {
-               MonoMethod *invoke = mono_marshal_get_runtime_invoke (mono_class_get_method_from_name_flags (mono_defaults.object_class, "Finalize", 0, 0), TRUE, FALSE);
+               MonoMethod *invoke = mono_marshal_get_runtime_invoke (mono_class_get_method_from_name_flags (mono_defaults.object_class, "Finalize", 0, 0), TRUE);
 
                domain->finalize_runtime_invoke = mono_compile_method (invoke);
        }
 
        runtime_invoke = (RuntimeInvokeFunction)domain->finalize_runtime_invoke;
 
-       mono_runtime_class_init (o->vtable);
+       mono_runtime_class_init_full (o->vtable, &error);
+       mono_error_raise_exception (&error); /* FIXME don't raise here */
 
        if (G_UNLIKELY (MONO_GC_FINALIZE_INVOKE_ENABLED ())) {
                MONO_GC_FINALIZE_INVOKE ((unsigned long)o, mono_object_get_size (o),
@@ -267,12 +272,14 @@ mono_gc_run_finalize (void *obj, void *data)
 void
 mono_gc_finalize_threadpool_threads (void)
 {
+       MonoError error;
        while (threads_to_finalize) {
                MonoInternalThread *thread = (MonoInternalThread*) mono_mlist_get_data (threads_to_finalize);
 
                /* Force finalization of the thread. */
                thread->threadpool_thread = FALSE;
-               mono_object_register_finalizer ((MonoObject*)thread);
+               mono_object_register_finalizer ((MonoObject*)thread, &error);
+               mono_error_assert_ok (&error); /* FIXME don't swallow the error */
 
                mono_gc_run_finalize (thread, NULL);
 
@@ -302,12 +309,16 @@ mono_gc_out_of_memory (size_t size)
  * since that, too, can cause the underlying pointer to be offset.
  */
 static void
-object_register_finalizer (MonoObject *obj, void (*callback)(void *, void*))
+object_register_finalizer (MonoObject *obj, void (*callback)(void *, void*), MonoError *error)
 {
        MonoDomain *domain;
 
-       if (obj == NULL)
-               mono_raise_exception (mono_get_exception_argument_null ("obj"));
+       mono_error_init (error);
+
+       if (obj == NULL) {
+               mono_error_set_argument_null (error, "obj", "");
+               return;
+       }
 
        domain = obj->vtable->domain;
 
@@ -349,10 +360,10 @@ object_register_finalizer (MonoObject *obj, void (*callback)(void *, void*))
  * 
  */
 void
-mono_object_register_finalizer (MonoObject *obj)
+mono_object_register_finalizer (MonoObject *obj, MonoError *error)
 {
        /* g_print ("Registered finalizer on %p %s.%s\n", obj, mono_object_class (obj)->name_space, mono_object_class (obj)->name); */
-       object_register_finalizer (obj, mono_gc_run_finalize);
+       object_register_finalizer (obj, mono_gc_run_finalize, error);
 }
 
 /**
@@ -470,14 +481,19 @@ ves_icall_System_GC_KeepAlive (MonoObject *obj)
 void
 ves_icall_System_GC_ReRegisterForFinalize (MonoObject *obj)
 {
+       MonoError error;
+
        MONO_CHECK_ARG_NULL (obj,);
 
-       object_register_finalizer (obj, mono_gc_run_finalize);
+       object_register_finalizer (obj, mono_gc_run_finalize, &error);
+       mono_error_set_pending_exception (&error);
 }
 
 void
 ves_icall_System_GC_SuppressFinalize (MonoObject *obj)
 {
+       MonoError error;
+
        MONO_CHECK_ARG_NULL (obj,);
 
        /* delegates have no finalizers, but we register them to deal with the
@@ -491,7 +507,8 @@ ves_icall_System_GC_SuppressFinalize (MonoObject *obj)
         * generated for it that needs cleaned up, but user wants to suppress
         * their derived object finalizer. */
 
-       object_register_finalizer (obj, NULL);
+       object_register_finalizer (obj, NULL, &error);
+       mono_error_set_pending_exception (&error);
 }
 
 void
@@ -623,6 +640,41 @@ mono_gc_finalize_notify (void)
        mono_coop_sem_post (&finalizer_sem);
 }
 
+/*
+This is the number of entries allowed in the hazard free queue before
+we explicitly cycle the finalizer thread to trigger pumping the queue.
+
+It was picked empirically by running the corlib test suite in a stress
+scenario where all hazard entries are queued.
+
+In this extreme scenario we double the number of times we cycle the finalizer
+thread compared to just GC calls.
+
+Entries are usually in the order of 100's of bytes each, so we're limiting
+floating garbage to be in the order of a dozen kb.
+*/
+static gboolean finalizer_thread_pulsed;
+#define HAZARD_QUEUE_OVERFLOW_SIZE 20
+
+static void
+hazard_free_queue_is_too_big (size_t size)
+{
+       if (size < HAZARD_QUEUE_OVERFLOW_SIZE)
+               return;
+
+       if (finalizer_thread_pulsed || InterlockedCompareExchange (&finalizer_thread_pulsed, TRUE, FALSE))
+               return;
+
+       mono_gc_finalize_notify ();
+}
+
+static void
+hazard_free_queue_pump (void)
+{
+       mono_thread_hazardous_try_free_all ();
+       finalizer_thread_pulsed = FALSE;
+}
+
 #ifdef HAVE_BOEHM_GC
 
 static void
@@ -698,6 +750,9 @@ finalizer_thread (gpointer unused)
 {
        gboolean wait = TRUE;
 
+       /* Register a hazard free queue pump callback */
+       mono_hazard_pointer_install_free_queue_size_callback (hazard_free_queue_is_too_big);
+
        while (!finished) {
                /* Wait to be notified that there's at least one
                 * finaliser to run
@@ -742,6 +797,8 @@ finalizer_thread (gpointer unused)
 
                reference_queue_proccess_all ();
 
+               hazard_free_queue_pump ();
+
                /* Avoid posting the pending done event until there are pending finalizers */
                if (mono_coop_sem_timedwait (&finalizer_sem, 0, MONO_SEM_FLAGS_NONE) == 0) {
                        /* Don't wait again at the start of the loop */
@@ -890,7 +947,7 @@ mono_gc_is_finalizer_internal_thread (MonoInternalThread *thread)
  * This routine tests whether the @thread argument represents the
  * finalization thread.
  * 
- * Returns true if @thread is the finalization thread.
+ * Returns: TRUE if @thread is the finalization thread.
  */
 gboolean
 mono_gc_is_finalizer_thread (MonoThread *thread)
@@ -914,15 +971,6 @@ mono_gc_get_mach_exception_thread (void)
 }
 #endif
 
-#ifndef HAVE_SGEN_GC
-void*
-mono_gc_alloc_mature (MonoVTable *vtable)
-{
-       return mono_object_new_specific (vtable);
-}
-#endif
-
-
 static MonoReferenceQueue *ref_queues;
 
 static void
@@ -1064,6 +1112,7 @@ mono_gc_reference_queue_new (mono_reference_queue_callback callback)
 gboolean
 mono_gc_reference_queue_add (MonoReferenceQueue *queue, MonoObject *obj, void *user_data)
 {
+       MonoError error;
        RefQueueEntry *entry;
        if (queue->should_be_deleted)
                return FALSE;
@@ -1073,7 +1122,8 @@ mono_gc_reference_queue_add (MonoReferenceQueue *queue, MonoObject *obj, void *u
        entry->domain = mono_object_domain (obj);
 
        entry->gchandle = mono_gchandle_new_weakref (obj, TRUE);
-       mono_object_register_finalizer (obj);
+       mono_object_register_finalizer (obj, &error);
+       mono_error_assert_ok (&error);
 
        ref_list_push (&queue->queue, entry);
        return TRUE;