Skip to content

Commit 8eb1189

Browse files
committed
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.
1 parent 7424fef commit 8eb1189

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed

Diff for: mono/metadata/gc-internal.h

+23
Original file line numberDiff line numberDiff line change
@@ -335,5 +335,28 @@ gboolean mono_gc_precise_stack_mark_enabled (void) MONO_INTERNAL;
335335

336336
FILE *mono_gc_get_logfile (void) MONO_INTERNAL;
337337

338+
typedef void (*mono_reference_queue_callback) (void *user_data);
339+
340+
typedef struct _MonoReferenceQueue MonoReferenceQueue;
341+
typedef struct _RefQueueEntry RefQueueEntry;
342+
343+
struct _RefQueueEntry {
344+
void *dis_link;
345+
void *user_data;
346+
RefQueueEntry *next;
347+
};
348+
349+
struct _MonoReferenceQueue {
350+
RefQueueEntry *queue;
351+
mono_reference_queue_callback callback;
352+
MonoReferenceQueue *next;
353+
gboolean should_be_deleted;
354+
};
355+
356+
MonoReferenceQueue* mono_gc_reference_queue_new (mono_reference_queue_callback callback) MONO_INTERNAL;
357+
void mono_gc_reference_queue_free (MonoReferenceQueue *queue) MONO_INTERNAL;
358+
gboolean mono_gc_reference_queue_add (MonoReferenceQueue *queue, MonoObject *obj, void *user_data) MONO_INTERNAL;
359+
360+
338361
#endif /* __MONO_METADATA_GC_INTERNAL_H__ */
339362

Diff for: mono/metadata/gc.c

+141
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ static gboolean finalizing_root_domain = FALSE;
5454
#define mono_finalizer_lock() EnterCriticalSection (&finalizer_mutex)
5555
#define mono_finalizer_unlock() LeaveCriticalSection (&finalizer_mutex)
5656
static CRITICAL_SECTION finalizer_mutex;
57+
static CRITICAL_SECTION reference_queue_mutex;
5758

5859
static GSList *domains_to_finalize= NULL;
5960
static MonoMList *threads_to_finalize = NULL;
@@ -64,6 +65,7 @@ static void object_register_finalizer (MonoObject *obj, void (*callback)(void *,
6465

6566
static void mono_gchandle_set_target (guint32 gchandle, MonoObject *obj);
6667

68+
static void reference_queue_proccess_all (void);
6769
#ifndef HAVE_NULL_GC
6870
static HANDLE pending_done_event;
6971
static HANDLE shutdown_event;
@@ -1065,6 +1067,8 @@ finalizer_thread (gpointer unused)
10651067
mono_attach_maybe_start ();
10661068
#endif
10671069

1070+
reference_queue_proccess_all ();
1071+
10681072
if (domains_to_finalize) {
10691073
mono_finalizer_lock ();
10701074
if (domains_to_finalize) {
@@ -1083,6 +1087,7 @@ finalizer_thread (gpointer unused)
10831087
*/
10841088
mono_gc_invoke_finalizers ();
10851089

1090+
10861091
SetEvent (pending_done_event);
10871092
}
10881093

@@ -1097,6 +1102,7 @@ mono_gc_init (void)
10971102
InitializeCriticalSection (&allocator_section);
10981103

10991104
InitializeCriticalSection (&finalizer_mutex);
1105+
InitializeCriticalSection (&reference_queue_mutex);
11001106

11011107
MONO_GC_REGISTER_ROOT_FIXED (gc_handles [HANDLE_NORMAL].entries);
11021108
MONO_GC_REGISTER_ROOT_FIXED (gc_handles [HANDLE_PINNED].entries);
@@ -1174,6 +1180,7 @@ mono_gc_cleanup (void)
11741180
DeleteCriticalSection (&handle_section);
11751181
DeleteCriticalSection (&allocator_section);
11761182
DeleteCriticalSection (&finalizer_mutex);
1183+
DeleteCriticalSection (&reference_queue_mutex);
11771184
}
11781185

11791186
#else
@@ -1292,3 +1299,137 @@ mono_gc_alloc_mature (MonoVTable *vtable)
12921299
return mono_object_new_specific (vtable);
12931300
}
12941301
#endif
1302+
1303+
1304+
static MonoReferenceQueue *ref_queues;
1305+
1306+
static void
1307+
ref_list_remove_element (RefQueueEntry **prev, RefQueueEntry *element)
1308+
{
1309+
do {
1310+
/* Guard if head is changed concurrently. */
1311+
while (*prev != element)
1312+
prev = &(*prev)->next;
1313+
} while (prev && InterlockedCompareExchangePointer ((void*)prev, element->next, element) != element);
1314+
}
1315+
1316+
static void
1317+
ref_list_push (RefQueueEntry **head, RefQueueEntry *value)
1318+
{
1319+
RefQueueEntry *current;
1320+
do {
1321+
current = *head;
1322+
value->next = current;
1323+
} while (InterlockedCompareExchangePointer ((void*)head, value, current) != current);
1324+
}
1325+
1326+
static void
1327+
reference_queue_proccess (MonoReferenceQueue *queue)
1328+
{
1329+
RefQueueEntry **iter = &queue->queue;
1330+
RefQueueEntry *entry;
1331+
while ((entry = *iter)) {
1332+
if (queue->should_be_deleted || !mono_gc_weak_link_get (&entry->dis_link)) {
1333+
ref_list_remove_element (iter, entry);
1334+
mono_gc_weak_link_remove (&entry->dis_link);
1335+
queue->callback (entry->user_data);
1336+
g_free (entry);
1337+
} else {
1338+
iter = &entry->next;
1339+
}
1340+
}
1341+
}
1342+
1343+
static void
1344+
reference_queue_proccess_all (void)
1345+
{
1346+
MonoReferenceQueue **iter;
1347+
MonoReferenceQueue *queue = ref_queues;
1348+
for (; queue; queue = queue->next)
1349+
reference_queue_proccess (queue);
1350+
1351+
restart:
1352+
EnterCriticalSection (&reference_queue_mutex);
1353+
for (iter = &ref_queues; *iter;) {
1354+
queue = *iter;
1355+
if (!queue->should_be_deleted) {
1356+
iter = &queue->next;
1357+
continue;
1358+
}
1359+
if (queue->queue) {
1360+
LeaveCriticalSection (&reference_queue_mutex);
1361+
reference_queue_proccess (queue);
1362+
goto restart;
1363+
}
1364+
*iter = queue->next;
1365+
g_free (queue);
1366+
}
1367+
LeaveCriticalSection (&reference_queue_mutex);
1368+
}
1369+
1370+
/**
1371+
* mono_gc_reference_queue_new:
1372+
* @callback callback used when processing dead entries.
1373+
*
1374+
* Create a new reference queue used to process collected objects.
1375+
* A reference queue let you queue the pair (managed object, user data).
1376+
* Once the managed object is collected @callback will be called
1377+
* in the finalizer thread with 'user data' as argument.
1378+
*
1379+
* The callback is called without any locks held.
1380+
*/
1381+
MonoReferenceQueue*
1382+
mono_gc_reference_queue_new (mono_reference_queue_callback callback)
1383+
{
1384+
MonoReferenceQueue *res = g_new0 (MonoReferenceQueue, 1);
1385+
res->callback = callback;
1386+
1387+
EnterCriticalSection (&reference_queue_mutex);
1388+
res->next = ref_queues;
1389+
ref_queues = res;
1390+
LeaveCriticalSection (&reference_queue_mutex);
1391+
1392+
return res;
1393+
}
1394+
1395+
/**
1396+
* mono_gc_reference_queue_add:
1397+
* @queue the queue to add the reference to.
1398+
* @obj the object to be watched for collection
1399+
* @user_data parameter to be passed to the queue callback
1400+
*
1401+
* Queue an object to be watched for collection.
1402+
*
1403+
* @returns false if the queue is scheduled to be freed.
1404+
*/
1405+
gboolean
1406+
mono_gc_reference_queue_add (MonoReferenceQueue *queue, MonoObject *obj, void *user_data)
1407+
{
1408+
RefQueueEntry *head;
1409+
RefQueueEntry *entry;
1410+
if (queue->should_be_deleted)
1411+
return FALSE;
1412+
1413+
entry = g_new0 (RefQueueEntry, 1);
1414+
entry->user_data = user_data;
1415+
mono_gc_weak_link_add (&entry->dis_link, obj, TRUE);
1416+
ref_list_push (&queue->queue, entry);
1417+
return TRUE;
1418+
}
1419+
1420+
/**
1421+
* mono_gc_reference_queue_free:
1422+
* @queue the queue that should be deleted.
1423+
*
1424+
* This operation signals that @queue should be deleted. This operation is deferred
1425+
* as it happens on the finalizer thread.
1426+
*
1427+
* After this call, no further objects can be queued. It's the responsibility of the
1428+
* caller to make sure that no further attempt to access queue will be made.
1429+
*/
1430+
void
1431+
mono_gc_reference_queue_free (MonoReferenceQueue *queue)
1432+
{
1433+
queue->should_be_deleted = TRUE;
1434+
}
1435+

0 commit comments

Comments
 (0)