Skip to content

Commit

Permalink
[marshal] Fix race between delegate marshaling and finalization
Browse files Browse the repository at this point in the history
When passing a delegate to native code, we pass the delegate_trampoline which can be used to invoke the delegate from native code and we store this mapping (delegate_trampoline->delegate) in a hashtable so we can get a delegate for the delegate_trampoline, when marshalling the opposite way. If we are trying to marshal a ftnptr (for managed code) to a delegate and we don't find this mapping we can crash. This could happen if the finalization for some other delegate that uses the same ftnptr removes this entry from the table. We solve this by never freeing these entries. The first delegate-ftnptr pair will always be alive. This is easier, faster and potentially uses less memory than tracking in the hashtable every pair. There should be a small and limited set of methods marshalled to native.

Fixes #13231
  • Loading branch information
BrzVlad authored and marek-safar committed May 31, 2019
1 parent bab25c0 commit e113ce9
Showing 1 changed file with 9 additions and 13 deletions.
22 changes: 9 additions & 13 deletions mono/metadata/marshal.c
Expand Up @@ -399,35 +399,31 @@ delegate_hash_table_new (void) {
static void
delegate_hash_table_remove (MonoDelegate *d)
{
guint32 gchandle = 0;
if (mono_gc_is_moving ())
return;

mono_marshal_lock ();
if (delegate_hash_table == NULL)
delegate_hash_table = delegate_hash_table_new ();
if (mono_gc_is_moving ())
gchandle = GPOINTER_TO_UINT (g_hash_table_lookup (delegate_hash_table, d->delegate_trampoline));
g_hash_table_remove (delegate_hash_table, d->delegate_trampoline);
mono_marshal_unlock ();
if (gchandle && mono_gc_is_moving ())
mono_gchandle_free_internal (gchandle);
}

static void
delegate_hash_table_add (MonoDelegateHandle d)
{
guint32 gchandle;
guint32 old_gchandle;

mono_marshal_lock ();
if (delegate_hash_table == NULL)
delegate_hash_table = delegate_hash_table_new ();
gpointer delegate_trampoline = MONO_HANDLE_GETVAL (d, delegate_trampoline);
if (mono_gc_is_moving ()) {
gchandle = mono_gchandle_new_weakref_from_handle (MONO_HANDLE_CAST (MonoObject, d));
old_gchandle = GPOINTER_TO_UINT (g_hash_table_lookup (delegate_hash_table, delegate_trampoline));
g_hash_table_insert (delegate_hash_table, delegate_trampoline, GUINT_TO_POINTER (gchandle));
if (old_gchandle)
mono_gchandle_free_internal (old_gchandle);
if (g_hash_table_lookup (delegate_hash_table, delegate_trampoline) == NULL) {
guint32 gchandle = mono_gchandle_from_handle (MONO_HANDLE_CAST (MonoObject, d), FALSE);
// This delegate will always be associated with its delegate_trampoline in the table.
// We don't free this delegate object because it is too expensive to keep track of these
// pairs and avoid races with the delegate finalization.
g_hash_table_insert (delegate_hash_table, delegate_trampoline, GUINT_TO_POINTER (gchandle));
}
} else {
g_hash_table_insert (delegate_hash_table, delegate_trampoline, MONO_HANDLE_RAW (d));
}
Expand Down

0 comments on commit e113ce9

Please sign in to comment.