From a9c7a7361d70e9742c2e17742815111eaf4938fa Mon Sep 17 00:00:00 2001 From: Rodrigo Kumpera Date: Tue, 1 Feb 2011 19:02:04 +0100 Subject: [PATCH] Implement a reference queue API. * gc.c: A reference queue allows one to queue callbcks for when objects are collected. It allows for safe cleanup of objects that can only be done when it is effectively collected. The major difference with regular finalization is that the collector makes sure the object was collected - and can't be resurrected. * gc-internal.h: Export entrypoints for the new API. --- mono/metadata/gc-internal.h | 67 +++++++++++++++++ mono/metadata/gc.c | 140 ++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+) diff --git a/mono/metadata/gc-internal.h b/mono/metadata/gc-internal.h index f80180ec06c2e..8f43c6c3e8a53 100644 --- a/mono/metadata/gc-internal.h +++ b/mono/metadata/gc-internal.h @@ -198,5 +198,72 @@ void *mono_gc_scan_object (void *obj) MONO_INTERNAL; /* Return the bitmap encoded by a descriptor */ gsize* mono_gc_get_bitmap_for_descr (void *descr, int *numbits) MONO_INTERNAL; +/* Return the suspend signal number used by the GC to suspend threads, + or -1 if not applicable. */ +int mono_gc_get_suspend_signal (void) MONO_INTERNAL; + +/* + * Return a human readable description of the GC in malloc-ed memory. + */ +char* mono_gc_get_description (void) MONO_INTERNAL; + +/* + * Configure the GC to desktop mode + */ +void mono_gc_set_desktop_mode (void) MONO_INTERNAL; + +/* + * Return whenever this GC can move objects + */ +gboolean mono_gc_is_moving (void) MONO_INTERNAL; + +typedef void* (*MonoGCLockedCallbackFunc) (void *data); + +void* mono_gc_invoke_with_gc_lock (MonoGCLockedCallbackFunc func, void *data) MONO_INTERNAL; + +int mono_gc_get_los_limit (void) MONO_INTERNAL; + +guint8* mono_gc_get_card_table (int *shift_bits, gpointer *card_mask) MONO_INTERNAL; + +void* mono_gc_get_nursery (int *shift_bits, size_t *size) MONO_INTERNAL; + +/* + * Return whenever GC is disabled + */ +gboolean mono_gc_is_disabled (void) MONO_INTERNAL; + +#if defined(__MACH__) +void mono_gc_register_mach_exception_thread (pthread_t thread) MONO_INTERNAL; +pthread_t mono_gc_get_mach_exception_thread (void) MONO_INTERNAL; +#endif + +gboolean mono_gc_parse_environment_string_extract_number (const char *str, glong *out) MONO_INTERNAL; + +gboolean mono_gc_precise_stack_mark_enabled (void) MONO_INTERNAL; + +FILE *mono_gc_get_logfile (void) MONO_INTERNAL; + +typedef void (*mono_reference_queue_callback) (void *user_data); + +typedef struct _MonoReferenceQueue MonoReferenceQueue; +typedef struct _RefQueueEntry RefQueueEntry; + +struct _RefQueueEntry { + void *dis_link; + void *user_data; + RefQueueEntry *next; +}; + +struct _MonoReferenceQueue { + RefQueueEntry *queue; + mono_reference_queue_callback callback; + MonoReferenceQueue *next; + gboolean should_be_deleted; +}; + +MonoReferenceQueue* mono_gc_reference_queue_new (mono_reference_queue_callback callback) MONO_INTERNAL; +void mono_gc_reference_queue_free (MonoReferenceQueue *queue) MONO_INTERNAL; +gboolean mono_gc_reference_queue_add (MonoReferenceQueue *queue, MonoObject *obj, void *user_data) MONO_INTERNAL; + #endif /* __MONO_METADATA_GC_INTERNAL_H__ */ diff --git a/mono/metadata/gc.c b/mono/metadata/gc.c index edbd47dfd18f8..81e784fcff5d8 100644 --- a/mono/metadata/gc.c +++ b/mono/metadata/gc.c @@ -52,6 +52,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; @@ -62,6 +63,7 @@ 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); #ifndef HAVE_NULL_GC static HANDLE pending_done_event; static HANDLE shutdown_event; @@ -1029,6 +1031,8 @@ finalizer_thread (gpointer unused) mono_attach_maybe_start (); #endif + reference_queue_proccess_all (); + if (domains_to_finalize) { mono_finalizer_lock (); if (domains_to_finalize) { @@ -1047,6 +1051,7 @@ finalizer_thread (gpointer unused) */ mono_gc_invoke_finalizers (); + SetEvent (pending_done_event); } @@ -1065,6 +1070,7 @@ mono_gc_init (void) InitializeCriticalSection (&allocator_section); InitializeCriticalSection (&finalizer_mutex); + InitializeCriticalSection (&reference_queue_mutex); MONO_GC_REGISTER_ROOT (gc_handles [HANDLE_NORMAL].entries); MONO_GC_REGISTER_ROOT (gc_handles [HANDLE_PINNED].entries); @@ -1141,6 +1147,7 @@ mono_gc_cleanup (void) DeleteCriticalSection (&handle_section); DeleteCriticalSection (&allocator_section); DeleteCriticalSection (&finalizer_mutex); + DeleteCriticalSection (&reference_queue_mutex); } #else @@ -1177,3 +1184,136 @@ mono_gc_is_finalizer_thread (MonoThread *thread) { return thread == gc_thread; } + + +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; + } while (InterlockedCompareExchangePointer ((void*)head, value, current) != current); +} + +static void +reference_queue_proccess (MonoReferenceQueue *queue) +{ + RefQueueEntry **iter = &queue->queue; + RefQueueEntry *entry; + while ((entry = *iter)) { + if (queue->should_be_deleted || !mono_gc_weak_link_get (&entry->dis_link)) { + ref_list_remove_element (iter, entry); + mono_gc_weak_link_remove (&entry->dis_link); + 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); +} + +/** + * 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 the pair (managed object, user data). + * 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. + * + * @returns false if the queue is scheduled to be freed. + */ +gboolean +mono_gc_reference_queue_add (MonoReferenceQueue *queue, MonoObject *obj, void *user_data) +{ + RefQueueEntry *head; + RefQueueEntry *entry; + if (queue->should_be_deleted) + return FALSE; + + entry = g_new0 (RefQueueEntry, 1); + entry->user_data = user_data; + mono_gc_weak_link_add (&entry->dis_link, obj, TRUE); + 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; +}