diff --git a/mono/metadata/gc-internal.h b/mono/metadata/gc-internal.h index 16d31f09e0add..fb2af22e49701 100644 --- a/mono/metadata/gc-internal.h +++ b/mono/metadata/gc-internal.h @@ -335,5 +335,28 @@ 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 fd29cd9dfe708..2208a7a7ff662 100644 --- a/mono/metadata/gc.c +++ b/mono/metadata/gc.c @@ -54,6 +54,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,6 +65,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; @@ -1065,6 +1067,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) { @@ -1083,6 +1087,7 @@ finalizer_thread (gpointer unused) */ mono_gc_invoke_finalizers (); + SetEvent (pending_done_event); } @@ -1097,6 +1102,7 @@ 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); @@ -1174,6 +1180,7 @@ mono_gc_cleanup (void) DeleteCriticalSection (&handle_section); DeleteCriticalSection (&allocator_section); DeleteCriticalSection (&finalizer_mutex); + DeleteCriticalSection (&reference_queue_mutex); } #else @@ -1292,3 +1299,137 @@ 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; + } 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; +} +