Merge pull request #495 from nicolas-raoul/fix-for-issue2907-with-no-formatting-changes
[mono.git] / mono / metadata / gc.c
index fd29cd9dfe708321bbf2fca1cce16a32883d243e..c6e01da6f6ef32d68c6ae1474bb227da0ac1b85c 100644 (file)
@@ -5,6 +5,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)
  */
 
 #include <config.h>
@@ -23,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>
@@ -30,6 +32,8 @@
 #include <mono/metadata/attach.h>
 #include <mono/metadata/console-io.h>
 #include <mono/utils/mono-semaphore.h>
+#include <mono/utils/mono-memory-model.h>
+#include <mono/utils/mono-counters.h>
 
 #ifndef HOST_WIN32
 #include <pthread.h>
@@ -54,6 +58,7 @@ static gboolean finalizing_root_domain = FALSE;
 #define mono_finalizer_lock() EnterCriticalSection (&finalizer_mutex)
 #define mono_finalizer_unlock() LeaveCriticalSection (&finalizer_mutex)
 static CRITICAL_SECTION finalizer_mutex;
+static CRITICAL_SECTION reference_queue_mutex;
 
 static GSList *domains_to_finalize= NULL;
 static MonoMList *threads_to_finalize = NULL;
@@ -64,11 +69,16 @@ static void object_register_finalizer (MonoObject *obj, void (*callback)(void *,
 
 static void mono_gchandle_set_target (guint32 gchandle, MonoObject *obj);
 
+static void reference_queue_proccess_all (void);
+static void mono_reference_queue_cleanup (void);
+static void reference_queue_clear_for_domain (MonoDomain *domain);
 #ifndef HAVE_NULL_GC
 static HANDLE pending_done_event;
 static HANDLE shutdown_event;
 #endif
 
+GCStats gc_stats;
+
 static void
 add_thread_to_finalize (MonoInternalThread *thread)
 {
@@ -219,9 +229,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);
 }
@@ -343,6 +352,7 @@ mono_domain_finalize (MonoDomain *domain, guint32 timeout)
        DomainFinalizationReq *req;
        guint32 res;
        HANDLE done_event;
+       MonoInternalThread *thread = mono_thread_internal_current ();
 
        if (mono_thread_internal_current () == gc_thread)
                /* We are called from inside a finalizer, not much we can do here */
@@ -383,12 +393,19 @@ mono_domain_finalize (MonoDomain *domain, guint32 timeout)
        if (timeout == -1)
                timeout = INFINITE;
 
-       res = WaitForSingleObjectEx (done_event, timeout, TRUE);
+       while (TRUE) {
+               res = WaitForSingleObjectEx (done_event, timeout, TRUE);
+               /* printf ("WAIT RES: %d.\n", res); */
 
-       /* printf ("WAIT RES: %d.\n", res); */
-       if (res == WAIT_TIMEOUT) {
-               /* We leak the handle here */
-               return FALSE;
+               if (res == WAIT_IO_COMPLETION) {
+                       if ((thread->state & (ThreadState_StopRequested | ThreadState_SuspendRequested)) != 0)
+                               return FALSE;
+               } else if (res == WAIT_TIMEOUT) {
+                       /* We leak the handle here */
+                       return FALSE;
+               } else {
+                       break;
+               }
        }
 
        CloseHandle (done_event);
@@ -471,6 +488,13 @@ ves_icall_System_GC_WaitForPendingFinalizers (void)
                /* Avoid deadlocks */
                return;
 
+       /*
+       If the finalizer thread is not live, lets pretend no finalizers are pending since the current thread might
+       be the one responsible for starting it up.
+       */
+       if (gc_thread == NULL)
+               return;
+
        ResetEvent (pending_done_event);
        mono_gc_finalize_notify ();
        /* g_print ("Waiting for pending finalizers....\n"); */
@@ -605,6 +629,16 @@ find_first_unset (guint32 bitmap)
        return -1;
 }
 
+static void*
+make_root_descr_all_refs (int numbits, gboolean pinned)
+{
+#ifdef HAVE_SGEN_GC
+       if (pinned)
+               return NULL;
+#endif
+       return mono_gc_make_root_descr_all_refs (numbits);
+}
+
 static guint32
 alloc_handle (HandleData *handles, MonoObject *obj, gboolean track)
 {
@@ -614,7 +648,7 @@ alloc_handle (HandleData *handles, MonoObject *obj, gboolean track)
        if (!handles->size) {
                handles->size = 32;
                if (handles->type > HANDLE_WEAK_TRACK) {
-                       handles->entries = mono_gc_alloc_fixed (sizeof (gpointer) * handles->size, mono_gc_make_root_descr_all_refs (handles->size));
+                       handles->entries = mono_gc_alloc_fixed (sizeof (gpointer) * handles->size, make_root_descr_all_refs (handles->size, handles->type == HANDLE_PINNED));
                } else {
                        handles->entries = g_malloc0 (sizeof (gpointer) * handles->size);
                        handles->domain_ids = g_malloc0 (sizeof (guint16) * handles->size);
@@ -652,8 +686,8 @@ alloc_handle (HandleData *handles, MonoObject *obj, gboolean track)
                if (handles->type > HANDLE_WEAK_TRACK) {
                        gpointer *entries;
 
-                       entries = mono_gc_alloc_fixed (sizeof (gpointer) * new_size, mono_gc_make_root_descr_all_refs (new_size));
-                       memcpy (entries, handles->entries, sizeof (gpointer) * handles->size);
+                       entries = mono_gc_alloc_fixed (sizeof (gpointer) * new_size, make_root_descr_all_refs (new_size, handles->type == HANDLE_PINNED));
+                       mono_gc_memmove (entries, handles->entries, sizeof (gpointer) * handles->size);
 
                        mono_gc_free_fixed (handles->entries);
                        handles->entries = entries;
@@ -664,8 +698,8 @@ alloc_handle (HandleData *handles, MonoObject *obj, gboolean track)
                        entries = g_malloc (sizeof (gpointer) * new_size);
                        /* we disable GC because we could lose some disappearing link updates */
                        mono_gc_disable ();
-                       memcpy (entries, handles->entries, sizeof (gpointer) * handles->size);
-                       memset (entries + handles->size, 0, sizeof (gpointer) * handles->size);
+                       mono_gc_memmove (entries, handles->entries, sizeof (gpointer) * handles->size);
+                       mono_gc_bzero (entries + handles->size, sizeof (gpointer) * handles->size);
                        memcpy (domain_ids, handles->domain_ids, sizeof (guint16) * handles->size);
                        for (i = 0; i < handles->size; ++i) {
                                MonoObject *obj = mono_gc_weak_link_get (&(handles->entries [i]));
@@ -699,7 +733,9 @@ alloc_handle (HandleData *handles, MonoObject *obj, gboolean track)
                        mono_gc_weak_link_add (&(handles->entries [slot]), obj, track);
        }
 
+#ifndef DISABLE_PERFCOUNTERS
        mono_perfcounters->gc_num_handles++;
+#endif
        unlock_handles (handles);
        /*g_print ("allocated entry %d of type %d to object %p (in slot: %p)\n", slot, handles->type, obj, handles->entries [slot]);*/
        res = (slot << 3) | (handles->type + 1);
@@ -907,7 +943,9 @@ mono_gchandle_free (guint32 gchandle)
        } else {
                /* print a warning? */
        }
+#ifndef DISABLE_PERFCOUNTERS
        mono_perfcounters->gc_num_handles--;
+#endif
        /*g_print ("freed entry %d of type %d\n", slot, handles->type);*/
        unlock_handles (handles);
        mono_profiler_gc_handle (MONO_PROFILER_GC_HANDLE_DESTROYED, handles->type, gchandle, NULL);
@@ -999,6 +1037,15 @@ finalize_domain_objects (DomainFinalizationReq *req)
 {
        MonoDomain *domain = req->domain;
 
+#if HAVE_SGEN_GC
+#define NUM_FOBJECTS 64
+       MonoObject *to_finalize [NUM_FOBJECTS];
+       int count;
+#endif
+
+       /* Process finalizers which are already in the queue */
+       mono_gc_invoke_finalizers ();
+
 #ifdef HAVE_BOEHM_GC
        while (g_hash_table_size (domain->finalizable_objects_hash) > 0) {
                int i;
@@ -1021,9 +1068,6 @@ finalize_domain_objects (DomainFinalizationReq *req)
                g_ptr_array_free (objs, TRUE);
        }
 #elif defined(HAVE_SGEN_GC)
-#define NUM_FOBJECTS 64
-       MonoObject *to_finalize [NUM_FOBJECTS];
-       int count;
        while ((count = mono_gc_finalizers_for_domain (domain, to_finalize, NUM_FOBJECTS))) {
                int i;
                for (i = 0; i < count; ++i) {
@@ -1032,9 +1076,9 @@ finalize_domain_objects (DomainFinalizationReq *req)
        }
 #endif
 
-       /* Process finalizers which are already in the queue */
-       mono_gc_invoke_finalizers ();
-
+       /* cleanup the reference queue */
+       reference_queue_clear_for_domain (domain);
+       
        /* printf ("DONE.\n"); */
        SetEvent (req->done_event);
 
@@ -1052,13 +1096,15 @@ finalizer_thread (gpointer unused)
 
                g_assert (mono_domain_get () == mono_get_root_domain ());
 
+               /* An alertable wait is required so this thread can be suspended on windows */
 #ifdef MONO_HAS_SEMAPHORES
-               MONO_SEM_WAIT (&finalizer_sem);
+               MONO_SEM_WAIT_ALERTABLE (&finalizer_sem, TRUE);
 #else
-               /* Use alertable=FALSE since we will be asked to exit using the event too */
-               WaitForSingleObjectEx (finalizer_event, INFINITE, FALSE);
+               WaitForSingleObjectEx (finalizer_event, INFINITE, TRUE);
 #endif
 
+               mono_threads_perform_thread_dump ();
+
                mono_console_handle_async_ops ();
 
 #ifndef DISABLE_ATTACH
@@ -1083,6 +1129,8 @@ finalizer_thread (gpointer unused)
                 */
                mono_gc_invoke_finalizers ();
 
+               reference_queue_proccess_all ();
+
                SetEvent (pending_done_event);
        }
 
@@ -1090,6 +1138,16 @@ finalizer_thread (gpointer unused)
        return 0;
 }
 
+#ifndef LAZY_GC_THREAD_CREATION
+static
+#endif
+void
+mono_gc_init_finalizer_thread (void)
+{
+       gc_thread = mono_thread_create_internal (mono_domain_get (), finalizer_thread, NULL, FALSE, 0);
+       ves_icall_System_Threading_Thread_SetName_internal (gc_thread, mono_string_new (mono_domain_get (), "Finalizer"));
+}
+
 void
 mono_gc_init (void)
 {
@@ -1097,10 +1155,16 @@ mono_gc_init (void)
        InitializeCriticalSection (&allocator_section);
 
        InitializeCriticalSection (&finalizer_mutex);
+       InitializeCriticalSection (&reference_queue_mutex);
 
        MONO_GC_REGISTER_ROOT_FIXED (gc_handles [HANDLE_NORMAL].entries);
        MONO_GC_REGISTER_ROOT_FIXED (gc_handles [HANDLE_PINNED].entries);
 
+       mono_counters_register ("Minor GC collections", MONO_COUNTER_GC | MONO_COUNTER_INT, &gc_stats.minor_gc_count);
+       mono_counters_register ("Major GC collections", MONO_COUNTER_GC | MONO_COUNTER_INT, &gc_stats.major_gc_count);
+       mono_counters_register ("Minor GC time", MONO_COUNTER_GC | MONO_COUNTER_TIME_INTERVAL, &gc_stats.minor_gc_time_usecs);
+       mono_counters_register ("Major GC time", MONO_COUNTER_GC | MONO_COUNTER_TIME_INTERVAL, &gc_stats.major_gc_time_usecs);
+
        mono_gc_base_init ();
 
        if (mono_gc_is_disabled ()) {
@@ -1118,8 +1182,9 @@ mono_gc_init (void)
        MONO_SEM_INIT (&finalizer_sem, 0);
 #endif
 
-       gc_thread = mono_thread_create_internal (mono_domain_get (), finalizer_thread, NULL, FALSE);
-       ves_icall_System_Threading_Thread_SetName_internal (gc_thread, mono_string_new (mono_domain_get (), "Finalizer"));
+#ifndef LAZY_GC_THREAD_CREATION
+       mono_gc_init_finalizer_thread ();
+#endif
 }
 
 void
@@ -1171,9 +1236,12 @@ mono_gc_cleanup (void)
 #endif
        }
 
+       mono_reference_queue_cleanup ();
+
        DeleteCriticalSection (&handle_section);
        DeleteCriticalSection (&allocator_section);
        DeleteCriticalSection (&finalizer_mutex);
+       DeleteCriticalSection (&reference_queue_mutex);
 }
 
 #else
@@ -1253,7 +1321,12 @@ mono_gc_parse_environment_string_extract_number (const char *str, glong *out)
        gboolean is_suffix = FALSE;
        char suffix;
 
-       switch (str [len - 1]) {
+       if (!len)
+               return FALSE;
+
+       suffix = str [len - 1];
+
+       switch (suffix) {
                case 'g':
                case 'G':
                        shift += 10;
@@ -1264,7 +1337,10 @@ mono_gc_parse_environment_string_extract_number (const char *str, glong *out)
                case 'K':
                        shift += 10;
                        is_suffix = TRUE;
-                       suffix = str [len - 1];
+                       break;
+               default:
+                       if (!isdigit (suffix))
+                               return FALSE;
                        break;
        }
 
@@ -1276,9 +1352,19 @@ mono_gc_parse_environment_string_extract_number (const char *str, glong *out)
                return FALSE;
 
        if (is_suffix) {
+               gulong unshifted;
+
+               if (val < 0)    /* negative numbers cannot be suffixed */
+                       return FALSE;
                if (*(endptr + 1)) /* Invalid string. */
                        return FALSE;
+
+               unshifted = (gulong)val;
                val <<= shift;
+               if (val < 0)    /* overflow */
+                       return FALSE;
+               if (((gulong)val >> shift) != unshifted) /* value too large */
+                       return FALSE;
        }
 
        *out = val;
@@ -1292,3 +1378,300 @@ mono_gc_alloc_mature (MonoVTable *vtable)
        return mono_object_new_specific (vtable);
 }
 #endif
+
+
+static MonoReferenceQueue *ref_queues;
+
+static void
+ref_list_remove_element (RefQueueEntry **prev, RefQueueEntry *element)
+{
+       do {
+               /* Guard if head is changed concurrently. */
+               while (*prev != element)
+                       prev = &(*prev)->next;
+       } while (prev && InterlockedCompareExchangePointer ((void*)prev, element->next, element) != element);
+}
+
+static void
+ref_list_push (RefQueueEntry **head, RefQueueEntry *value)
+{
+       RefQueueEntry *current;
+       do {
+               current = *head;
+               value->next = current;
+               STORE_STORE_FENCE; /*Must make sure the previous store is visible before the CAS. */
+       } while (InterlockedCompareExchangePointer ((void*)head, value, current) != current);
+}
+
+static void
+reference_queue_proccess (MonoReferenceQueue *queue)
+{
+       RefQueueEntry **iter = &queue->queue;
+       RefQueueEntry *entry;
+       while ((entry = *iter)) {
+#ifdef HAVE_SGEN_GC
+               if (queue->should_be_deleted || !mono_gc_weak_link_get (&entry->dis_link)) {
+                       mono_gc_weak_link_remove (&entry->dis_link);
+#else
+               if (queue->should_be_deleted || !mono_gchandle_get_target (entry->gchandle)) {
+                       mono_gchandle_free ((guint32)entry->gchandle);
+#endif
+                       ref_list_remove_element (iter, entry);
+                       queue->callback (entry->user_data);
+                       g_free (entry);
+               } else {
+                       iter = &entry->next;
+               }
+       }
+}
+
+static void
+reference_queue_proccess_all (void)
+{
+       MonoReferenceQueue **iter;
+       MonoReferenceQueue *queue = ref_queues;
+       for (; queue; queue = queue->next)
+               reference_queue_proccess (queue);
+
+restart:
+       EnterCriticalSection (&reference_queue_mutex);
+       for (iter = &ref_queues; *iter;) {
+               queue = *iter;
+               if (!queue->should_be_deleted) {
+                       iter = &queue->next;
+                       continue;
+               }
+               if (queue->queue) {
+                       LeaveCriticalSection (&reference_queue_mutex);
+                       reference_queue_proccess (queue);
+                       goto restart;
+               }
+               *iter = queue->next;
+               g_free (queue);
+       }
+       LeaveCriticalSection (&reference_queue_mutex);
+}
+
+static void
+mono_reference_queue_cleanup (void)
+{
+       MonoReferenceQueue *queue = ref_queues;
+       for (; queue; queue = queue->next)
+               queue->should_be_deleted = TRUE;
+       reference_queue_proccess_all ();
+}
+
+static void
+reference_queue_clear_for_domain (MonoDomain *domain)
+{
+       MonoReferenceQueue *queue = ref_queues;
+       for (; queue; queue = queue->next) {
+               RefQueueEntry **iter = &queue->queue;
+               RefQueueEntry *entry;
+               while ((entry = *iter)) {
+                       MonoObject *obj;
+#ifdef HAVE_SGEN_GC
+                       obj = mono_gc_weak_link_get (&entry->dis_link);
+                       if (obj && mono_object_domain (obj) == domain) {
+                               mono_gc_weak_link_remove (&entry->dis_link);
+#else
+                       obj = mono_gchandle_get_target (entry->gchandle);
+                       if (obj && mono_object_domain (obj) == domain) {
+                               mono_gchandle_free ((guint32)entry->gchandle);
+#endif
+                               ref_list_remove_element (iter, entry);
+                               queue->callback (entry->user_data);
+                               g_free (entry);
+                       } else {
+                               iter = &entry->next;
+                       }
+               }
+       }
+}
+/**
+ * mono_gc_reference_queue_new:
+ * @callback callback used when processing dead entries.
+ *
+ * Create a new reference queue used to process collected objects.
+ * A reference queue let you queue a pair (managed object, user data)
+ * using the mono_gc_reference_queue_add method.
+ *
+ * Once the managed object is collected @callback will be called
+ * in the finalizer thread with 'user data' as argument.
+ *
+ * The callback is called without any locks held.
+ */
+MonoReferenceQueue*
+mono_gc_reference_queue_new (mono_reference_queue_callback callback)
+{
+       MonoReferenceQueue *res = g_new0 (MonoReferenceQueue, 1);
+       res->callback = callback;
+
+       EnterCriticalSection (&reference_queue_mutex);
+       res->next = ref_queues;
+       ref_queues = res;
+       LeaveCriticalSection (&reference_queue_mutex);
+
+       return res;
+}
+
+/**
+ * mono_gc_reference_queue_add:
+ * @queue the queue to add the reference to.
+ * @obj the object to be watched for collection
+ * @user_data parameter to be passed to the queue callback
+ *
+ * Queue an object to be watched for collection, when the @obj is
+ * collected, the callback that was registered for the @queue will
+ * be invoked with the @obj and @user_data arguments.
+ *
+ * @returns false if the queue is scheduled to be freed.
+ */
+gboolean
+mono_gc_reference_queue_add (MonoReferenceQueue *queue, MonoObject *obj, void *user_data)
+{
+       RefQueueEntry *entry;
+       if (queue->should_be_deleted)
+               return FALSE;
+
+       entry = g_new0 (RefQueueEntry, 1);
+       entry->user_data = user_data;
+
+#ifdef HAVE_SGEN_GC
+       mono_gc_weak_link_add (&entry->dis_link, obj, TRUE);
+#else
+       entry->gchandle = mono_gchandle_new_weakref (obj, TRUE);
+       mono_object_register_finalizer (obj);
+#endif
+
+       ref_list_push (&queue->queue, entry);
+       return TRUE;
+}
+
+/**
+ * mono_gc_reference_queue_free:
+ * @queue the queue that should be deleted.
+ *
+ * This operation signals that @queue should be deleted. This operation is deferred
+ * as it happens on the finalizer thread.
+ *
+ * After this call, no further objects can be queued. It's the responsibility of the
+ * caller to make sure that no further attempt to access queue will be made.
+ */
+void
+mono_gc_reference_queue_free (MonoReferenceQueue *queue)
+{
+       queue->should_be_deleted = TRUE;
+}
+
+#define ptr_mask ((sizeof (void*) - 1))
+#define _toi(ptr) ((size_t)ptr)
+#define unaligned_bytes(ptr) (_toi(ptr) & ptr_mask)
+#define align_down(ptr) ((void*)(_toi(ptr) & ~ptr_mask))
+#define align_up(ptr) ((void*) ((_toi(ptr) + ptr_mask) & ~ptr_mask))
+
+/**
+ * mono_gc_bzero:
+ * @dest: address to start to clear
+ * @size: size of the region to clear
+ *
+ * Zero @size bytes starting at @dest.
+ *
+ * Use this to zero memory that can hold managed pointers.
+ *
+ * FIXME borrow faster code from some BSD libc or bionic
+ */
+void
+mono_gc_bzero (void *dest, size_t size)
+{
+       char *p = (char*)dest;
+       char *end = p + size;
+       char *align_end = align_up (p);
+       char *word_end;
+
+       while (p < align_end)
+               *p++ = 0;
+
+       word_end = align_down (end);
+       while (p < word_end) {
+               *((void**)p) = NULL;
+               p += sizeof (void*);
+       }
+
+       while (p < end)
+               *p++ = 0;
+}
+
+
+/**
+ * mono_gc_memmove:
+ * @dest: destination of the move
+ * @src: source
+ * @size: size of the block to move
+ *
+ * Move @size bytes from @src to @dest.
+ * size MUST be a multiple of sizeof (gpointer)
+ *
+ * FIXME borrow faster code from some BSD libc or bionic
+ */
+void
+mono_gc_memmove (void *dest, const void *src, size_t size)
+{
+       /*
+        * If dest and src are differently aligned with respect to
+        * pointer size then it makes no sense to do aligned copying.
+        * In fact, we would end up with unaligned loads which is
+        * incorrect on some architectures.
+        */
+       if ((char*)dest - (char*)align_down (dest) != (char*)src - (char*)align_down (src)) {
+               memmove (dest, src, size);
+               return;
+       }
+
+       /*
+        * A bit of explanation on why we align only dest before doing word copies.
+        * Pointers to managed objects must always be stored in word aligned addresses, so
+        * even if dest is misaligned, src will be by the same amount - this ensure proper atomicity of reads.
+        */
+       if (dest > src && ((size_t)((char*)dest - (char*)src) < size)) {
+               char *p = (char*)dest + size;
+               char *s = (char*)src + size;
+               char *start = (char*)dest;
+               char *align_end = MAX((char*)dest, (char*)align_down (p));
+               char *word_start;
+
+               while (p > align_end)
+                       *--p = *--s;
+
+               word_start = align_up (start);
+               while (p > word_start) {
+                       p -= sizeof (void*);
+                       s -= sizeof (void*);
+                       *((void**)p) = *((void**)s);
+               }
+
+               while (p > start)
+                       *--p = *--s;
+       } else {
+               char *p = (char*)dest;
+               char *s = (char*)src;
+               char *end = p + size;
+               char *align_end = MIN ((char*)end, (char*)align_up (p));
+               char *word_end;
+
+               while (p < align_end)
+                       *p++ = *s++;
+
+               word_end = align_down (end);
+               while (p < word_end) {
+                       *((void**)p) = *((void**)s);
+                       p += sizeof (void*);
+                       s += sizeof (void*);
+               }
+
+               while (p < end)
+                       *p++ = *s++;
+       }
+}
+
+