Implement oom handling for MS.
authorRodrigo Kumpera <kumpera@gmail.com>
Fri, 5 Nov 2010 18:39:14 +0000 (16:39 -0200)
committerRodrigo Kumpera <kumpera@gmail.com>
Wed, 10 Nov 2010 19:35:50 +0000 (17:35 -0200)
* sgen-gc.c (collect_nursery): If any object was pinned
after initial pinning, sort the queue so build_nursery_fragments
will see the aditional objects.
If pinning due to OOM happened ask for a major collection.

* sgen-gc.c (major_do_collection): Adjust the pin queue if
OOM happened. Here we have to clean discarded entries before
optimizing the queue or we might end with interior pointers
in the mix.

* sgen-gc.c (sgen_collect_major_no_lock): New function so
major collectors can trigger a collection. They can't
use public API since the gc lock is not meant to be reentrant.

* sgen-major-copy-object.h (copy_object_no_checks): Pin
the object if we can't promote it.

* sgen-marksweep.c (alloc_obj): Return null if we fail
to allocate a block for a given freelist size.

* sgen-marksweep.c (major_alloc_degraded): Ditto.

* sgen-marksweep.c (major_copy_or_mark_object): Handle OOM.

* sgen-marksweep.c (major_alloc_small_pinned_obj): If allocation
fails, try a major collection since this is memory requested by
the runtime and it's much harder to handle failure.

mono/metadata/sgen-gc.c
mono/metadata/sgen-gc.h
mono/metadata/sgen-major-copy-object.h
mono/metadata/sgen-marksweep.c
mono/metadata/sgen-pinning.c

index 1f6feb7c5f0d2caed4c45c0152c1e732ca2ef116..9f1fdbbd51a41d8a7a74eb2b071a10fe68ac4b24 100644 (file)
@@ -514,10 +514,14 @@ static mword highest_heap_address = 0;
 
 static LOCK_DECLARE (interruption_mutex);
 static LOCK_DECLARE (global_remset_mutex);
+static LOCK_DECLARE (pin_queue_mutex);
 
 #define LOCK_GLOBAL_REMSET pthread_mutex_lock (&global_remset_mutex)
 #define UNLOCK_GLOBAL_REMSET pthread_mutex_unlock (&global_remset_mutex)
 
+#define LOCK_PIN_QUEUE pthread_mutex_lock (&pin_queue_mutex)
+#define UNLOCK_PIN_QUEUE pthread_mutex_unlock (&pin_queue_mutex)
+
 typedef struct _FinalizeEntry FinalizeEntry;
 struct _FinalizeEntry {
        FinalizeEntry *next;
@@ -714,6 +718,9 @@ static MonoVTable *array_fill_vtable;
 static mword max_heap_size = ((mword)0)- ((mword)1);
 static mword allocated_heap;
 
+/*Object was pinned during the current collection*/
+static mword objects_pinned;
+
 void
 mono_sgen_release_space (mword size, int space)
 {
@@ -1804,6 +1811,24 @@ mono_sgen_pin_objects_in_section (GCMemSection *section, GrayQueue *queue)
        }
 }
 
+
+void
+mono_sgen_pin_object (void *object, GrayQueue *queue)
+{
+       if (major_collector.is_parallel) {
+               LOCK_PIN_QUEUE;
+               /*object arrives pinned*/
+               pin_stage_ptr (object);
+               ++objects_pinned ;
+               UNLOCK_PIN_QUEUE;
+       } else {
+               SGEN_PIN_OBJECT (object);
+               pin_stage_ptr (object);
+               ++objects_pinned;
+       }
+       GRAY_OBJECT_ENQUEUE (queue, object);
+}
+
 /* Sort the addresses in array in increasing order.
  * Done using a by-the book heap sort. Which has decent and stable performance, is pretty cache efficient.
  */
@@ -2661,6 +2686,7 @@ need_major_collection (mword space_needed)
 static gboolean
 collect_nursery (size_t requested_size)
 {
+       gboolean needs_major;
        size_t max_garbage_amount;
        char *orig_nursery_next;
        TV_DECLARE (all_atv);
@@ -2676,6 +2702,7 @@ collect_nursery (size_t requested_size)
        check_scan_starts ();
 
        degraded_mode = 0;
+       objects_pinned = 0;
        orig_nursery_next = nursery_next;
        nursery_next = MAX (nursery_next, nursery_last_pinned_end);
        /* FIXME: optimize later to use the higher address where an object can be present */
@@ -2765,6 +2792,13 @@ collect_nursery (size_t requested_size)
        time_minor_finish_gray_stack += TV_ELAPSED_MS (btv, atv);
        mono_profiler_gc_event (MONO_GC_EVENT_MARK_END, 0);
 
+       if (objects_pinned) {
+               evacuate_pin_staging_area ();
+               optimize_pin_queue (0);
+               nursery_section->pin_queue_start = pin_queue;
+               nursery_section->pin_queue_num_entries = next_pin_slot;
+       }
+
        /* walk the pin_queue, build up the fragment list of free memory, unmark
         * pinned objects as we go, memzero() the empty fragments so they are ready for the
         * next allocations.
@@ -2805,9 +2839,12 @@ collect_nursery (size_t requested_size)
 
        binary_protocol_flush_buffers (FALSE);
 
+       /*objects are late pinned because of lack of memory, so a major is a good call*/
+       needs_major = need_major_collection (0) || objects_pinned;
        current_collection_generation = -1;
+       objects_pinned = 0;
 
-       return need_major_collection ();
+       return needs_major;
 }
 
 static void
@@ -2825,6 +2862,7 @@ major_do_collection (const char *reason)
        char *heap_end = (char*)-1;
        int old_num_major_sections = major_collector.get_num_major_sections ();
        int num_major_sections, num_major_sections_saved, save_target, allowance_target;
+       int old_next_pin_slot;
        mword los_memory_saved, los_memory_alloced, old_los_memory_usage;
 
        mono_perfcounters->gc_collections1++;
@@ -2835,6 +2873,7 @@ major_do_collection (const char *reason)
         */
        los_memory_alloced = los_memory_usage - MIN (last_los_memory_usage, los_memory_usage);
        old_los_memory_usage = los_memory_usage;
+       objects_pinned = 0;
 
        //count_ref_nonref_objs ();
        //consistency_check ();
@@ -2915,6 +2954,7 @@ major_do_collection (const char *reason)
        /* second pass for the sections */
        mono_sgen_pin_objects_in_section (nursery_section, WORKERS_DISTRIBUTE_GRAY_QUEUE);
        major_collector.pin_objects (WORKERS_DISTRIBUTE_GRAY_QUEUE);
+       old_next_pin_slot = next_pin_slot;
 
        TV_GETTIME (btv);
        time_major_pinning += TV_ELAPSED_MS (atv, btv);
@@ -2971,6 +3011,15 @@ major_do_collection (const char *reason)
        TV_GETTIME (atv);
        time_major_finish_gray_stack += TV_ELAPSED_MS (btv, atv);
 
+       if (objects_pinned) {
+               /*This is slow, but we just OOM'd*/
+               mono_sgen_pin_queue_clear_discarded_entries (nursery_section, old_next_pin_slot);
+               evacuate_pin_staging_area ();
+               optimize_pin_queue (0);
+               mono_sgen_find_section_pin_queue_start_end (nursery_section);
+               objects_pinned = 0;
+       }
+
        /* sweep the big objects list */
        prevbo = NULL;
        for (bigobj = los_object_list; bigobj;) {
@@ -3080,6 +3129,16 @@ major_collection (const char *reason)
        current_collection_generation = -1;
 }
 
+void
+sgen_collect_major_no_lock (const char *reason)
+{
+        mono_profiler_gc_event (MONO_GC_EVENT_START, 1);
+        stop_world (1);
+        major_collection (reason);
+        restart_world (1);
+        mono_profiler_gc_event (MONO_GC_EVENT_END, 1);
+}
+
 /*
  * When deciding if it's better to collect or to expand, keep track
  * of how much garbage was reclaimed with the last collection: if it's too
@@ -3640,10 +3699,10 @@ mono_gc_alloc_string (MonoVTable *vtable, size_t size, gint32 len)
 void*
 mono_gc_alloc_pinned_obj (MonoVTable *vtable, size_t size)
 {
-       /* FIXME: handle OOM */
        void **p;
        size = ALIGN_UP (size);
        LOCK_GC;
+
        if (size > MAX_SMALL_OBJ_SIZE) {
                /* large objects are always pinned anyway */
                p = alloc_large_inner (vtable, size);
@@ -6594,6 +6653,7 @@ mono_gc_base_init (void)
 
        LOCK_INIT (interruption_mutex);
        LOCK_INIT (global_remset_mutex);
+       LOCK_INIT (pin_queue_mutex);
 
        if ((env = getenv ("MONO_GC_PARAMS"))) {
                opts = g_strsplit (env, ",", -1);
index 271ba7c654b0a53f0e141232c955ad799cdf24e8..9b55e3f3713b6a32c866edb5492d8af57e7ea439 100644 (file)
@@ -745,4 +745,5 @@ enum {
 gboolean mono_sgen_try_alloc_space (mword size, int space) MONO_INTERNAL;
 void mono_sgen_release_space (mword size, int space) MONO_INTERNAL;
 void mono_sgen_pin_object (void *object, SgenGrayQueue *queue) MONO_INTERNAL;
+void sgen_collect_major_no_lock (const char *reason) MONO_INTERNAL;;
 #endif /* __MONO_SGENGC_H__ */
index a23b2dd1721e3bf20103d5cd57a86c4be9c2a06d..d590fd9d1f1fb5f2b9f3f7c42f8f35d1fe3bb43d 100644 (file)
@@ -90,6 +90,11 @@ copy_object_no_checks (void *obj, SgenGrayQueue *queue)
        mword objsize = SGEN_ALIGN_UP (mono_sgen_par_object_get_size (vt, (MonoObject*)obj));
        char *destination = major_alloc_object (objsize, has_references);
 
+       if (G_UNLIKELY (!destination)) {
+               mono_sgen_pin_object (obj, queue);
+               return obj;
+       }
+
        par_copy_object_no_checks (destination, vt, obj, objsize, has_references ? queue : NULL);
 
        /* set the forwarding pointer */
index 161cd107fed4900db9ba4a984cca7b4a60cb3bed..26f5de3d5c4f886de95188919201d9662a932503 100644 (file)
@@ -558,8 +558,12 @@ alloc_obj (int size, gboolean pinned, gboolean has_references)
 
        LOCK_MS_BLOCK_LIST;
 
-       if (!free_blocks [size_index])
-               ms_alloc_block (size_index, pinned, has_references);
+       if (!free_blocks [size_index]) {
+               if (G_UNLIKELY (!ms_alloc_block (size_index, pinned, has_references))) {
+                       UNLOCK_MS_BLOCK_LIST;
+                       return NULL;
+               }
+       }
 
        block = free_blocks [size_index];
        DEBUG (9, g_assert (block));
@@ -628,7 +632,15 @@ major_free_non_pinned_object (char *obj, size_t size)
 static void*
 major_alloc_small_pinned_obj (size_t size, gboolean has_references)
 {
-       return alloc_obj (size, TRUE, has_references);
+        void *res = alloc_obj (size, TRUE, has_references);
+        /*If we failed to alloc memory, we better try releasing memory
+         *as pinned alloc is requested by the runtime.
+         */
+        if (!res) {
+                sgen_collect_major_no_lock ("pinned alloc failure");
+                res = alloc_obj (size, TRUE, has_references);
+        }
+        return res;
 }
 
 static void
@@ -646,11 +658,13 @@ major_alloc_degraded (MonoVTable *vtable, size_t size)
        void *obj;
        int old_num_sections = num_major_sections;
        obj = alloc_obj (size, FALSE, vtable->klass->has_references);
-       *(MonoVTable**)obj = vtable;
-       HEAVY_STAT (++stat_objects_alloced_degraded);
-       HEAVY_STAT (stat_bytes_alloced_degraded += size);
-       g_assert (num_major_sections >= old_num_sections);
-       mono_sgen_register_major_sections_alloced (num_major_sections - old_num_sections);
+       if (G_LIKELY (obj)) {
+               *(MonoVTable**)obj = vtable;
+               HEAVY_STAT (++stat_objects_alloced_degraded);
+               HEAVY_STAT (stat_bytes_alloced_degraded += size);
+               g_assert (num_major_sections >= old_num_sections);
+               mono_sgen_register_major_sections_alloced (num_major_sections - old_num_sections);
+       }
        return obj;
 }
 
@@ -849,6 +863,21 @@ major_copy_or_mark_object (void **ptr, SgenGrayQueue *queue)
                has_references = SGEN_VTABLE_HAS_REFERENCES (vt);
 
                destination = major_alloc_object (objsize, has_references);
+               if (G_UNLIKELY (!destination)) {
+                       /*
+                        * This can fail under 2 scenarios:
+                        *  - object was copied, we must update *ptr.
+                        *  - object was pinned, we can leave *ptr as is.
+                        */
+                       if (SGEN_CAS_PTR (obj, (void*)((mword)vt | SGEN_PINNED_BIT), vt) == vt) {
+                               mono_sgen_pin_object (obj, queue);
+                       } else {
+                               vtable_word = *(mword*)obj;
+                               if (vtable_word & SGEN_FORWARDED_BIT)
+                                       *ptr = (void*)(vtable_word & ~SGEN_VTABLE_BITS_MASK);
+                       }
+                       return;
+               }
 
                if (SGEN_CAS_PTR (obj, (void*)((mword)destination | SGEN_FORWARDED_BIT), vt) == vt) {
                        gboolean was_marked;
@@ -919,7 +948,7 @@ major_copy_or_mark_object (void **ptr, SgenGrayQueue *queue)
 
        if (ptr_in_nursery (obj)) {
                int word, bit;
-               char *forwarded;
+               char *forwarded, *old_obj;
 
                if ((forwarded = SGEN_OBJECT_IS_FORWARDED (obj))) {
                        *ptr = forwarded;
@@ -931,7 +960,11 @@ major_copy_or_mark_object (void **ptr, SgenGrayQueue *queue)
                HEAVY_STAT (++stat_objects_copied_major);
 
        do_copy_object:
+               old_obj = obj;
                obj = copy_object_no_checks (obj, queue);
+               if (G_UNLIKELY (old_obj == obj)) {
+                       return;
+               }
                *ptr = obj;
 
                /*
index a90cf3d71ad2500ac3a88fb57ecf63f925ca3ec4..bd28cdfd0b4e5403c057af5ee7ab722620fe950d 100644 (file)
@@ -125,3 +125,18 @@ mono_sgen_find_section_pin_queue_start_end (GCMemSection *section)
        section->pin_queue_start = mono_sgen_find_optimized_pin_queue_area (section->data, section->end_data, &section->pin_queue_num_entries);
        DEBUG (6, fprintf (gc_debug_file, "Found %d pinning addresses in section %p\n", section->pin_queue_num_entries, section));
 }
+
+static void
+mono_sgen_pin_queue_clear_discarded_entries (GCMemSection *section, int max_pin_slot)
+{
+       void **start = section->pin_queue_start + section->pin_queue_num_entries;
+       void **end = pin_queue + max_pin_slot;
+       void *addr;
+
+       for (; start < end; ++start) {
+               addr = *start;
+               if ((char*)addr < section->data || (char*)addr > section->end_data)
+                       break;
+               *start = NULL;
+       }
+}