Skip to content

Commit

Permalink
Implement a reference queue API.
Browse files Browse the repository at this point in the history
	* 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.
  • Loading branch information
kumpera committed Feb 1, 2011
1 parent 7424fef commit 8eb1189
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 0 deletions.
23 changes: 23 additions & 0 deletions mono/metadata/gc-internal.h
Expand Up @@ -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__ */

141 changes: 141 additions & 0 deletions mono/metadata/gc.c
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -1083,6 +1087,7 @@ finalizer_thread (gpointer unused)
*/
mono_gc_invoke_finalizers ();


SetEvent (pending_done_event);
}

Expand All @@ -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);
Expand Down Expand Up @@ -1174,6 +1180,7 @@ mono_gc_cleanup (void)
DeleteCriticalSection (&handle_section);
DeleteCriticalSection (&allocator_section);
DeleteCriticalSection (&finalizer_mutex);
DeleteCriticalSection (&reference_queue_mutex);
}

#else
Expand Down Expand Up @@ -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;
}

0 comments on commit 8eb1189

Please sign in to comment.