[mono] Native memory leak on the first invoke of every open-instance delegate to a virtual method
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 72/100
Direzione di ricerca
Inizia da src/mono/mono/metadata/marshal.c e leggi mono_marshal_get_delegate_invoke_internal, concentrandoti sui due ritorni dovuti a un riscontro nella cache dei wrapper che avvengono dopo la duplicazione di target_method_sig. Verifica a chi appartiene questa allocazione e come viene liberata in ciascun percorso, poi esegui i test pertinenti del runtime Mono. Il lavoro è completato quando l’uso ripetuto di delegate open-instance verso metodi virtuali non fa più aumentare la memoria nativa e i test esistenti passano.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Description
[Mono] Native leak in mono_marshal_get_delegate_invoke_internal: target_method_sig is never freed on a wrapper cache hit
Repository: dotnet/runtime. Area label: area-VM-meta-mono. OS: Android (Mono runtime).
Description
On Mono, every new open-instance delegate over a virtual method (including a property that implements an interface) leaks a duplicated MonoMethodSignature on its first invoke. Code that creates such delegates repeatedly leaks native memory without bound, and it shows up nowhere in the managed heap.
In src/mono/mono/metadata/marshal.c, mono_marshal_get_delegate_invoke_internal (tag v10.0.12, unchanged on main):
if (subtype == WRAPPER_SUBTYPE_DELEGATE_INVOKE_VIRTUAL) {
/*
* We don't want to use target_method's signature because it can be freed early
*/
target_method_sig = mono_metadata_signature_dup_delegate_invoke_to_target (invoke_sig); // g_malloc (image == NULL)
closed_over_null = sig->param_count == target_method_sig->param_count;
}
/*
* Check cache
*/
if (ctx) {
...
res = check_generic_delegate_wrapper_cache (cache, orig_method, method, ctx);
if (res)
return res; // target_method_sig leaked
...
} else if (callvirt) {
...
res = (MonoMethod *)g_hash_table_lookup (cache, invoke_sig);
mono_marshal_unlock ();
if (res)
return res; // target_method_sig leaked
...
}
mono_metadata_signature_dup_delegate_invoke_to_target calls mono_metadata_signature_dup_full (NULL, sig), which allocates with g_malloc. Nothing frees target_method_sig on the cache-hit returns (or anywhere else in the function). Since mono_delegate_trampoline disables caching for callvirt delegates, every new delegate instance of this kind reaches this code once, and after the first one the wrapper is always cached.
The copy was introduced between 7.0 and 8.0: v7.0.20 has no call to mono_metadata_signature_dup_delegate_invoke_to_target, v8.0.18, v9.0.7 and v10.0.12 do.
Reproduction Steps
On a .NET for Android app (Mono), e.g. net10.0-android, Release, on a device:
public interface IFoo { int Value { get; set; } }
public class Foo : IFoo { public int Value { get; set; } } // implements IFoo, so set_Value is virtual
public class Bar { public int Value { get; set; } } // control: non-virtual
static double LeakPerDelegate<T>() where T : new()
{
var setter = typeof(T).GetProperty("Value")!.GetSetMethod()!;
var target = new T();
for (var i = 0; i < 1_000; i++) // warm up: wrapper is now cached
((Action<T, int>)Delegate.CreateDelegate(typeof(Action<T, int>), null, setter))(target, i);
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
var before = Android.OS.Debug.NativeHeapAllocatedSize;
for (var i = 0; i < 100_000; i++)
((Action<T, int>)Delegate.CreateDelegate(typeof(Action<T, int>), null, setter))(target, i);
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
return (Android.OS.Debug.NativeHeapAllocatedSize - before) / 100_000.0;
}
Expected behavior
Native heap does not grow per delegate once the invoke wrapper is cached (as for Bar).
Actual behavior
Measured on a Samsung SM-X306B, Android 16, .NET for Android workload 36.1.69, Mono runtime pack Microsoft.NETCore.App.Runtime.Mono.android-arm64 10.0.12 (libmonosgen-2.0.so build id 54924a38a8872da3a0bb85f533898e89895508b7):
| Target | Native heap growth per delegate |
|---|---|
Foo (virtual setter) |
80 bytes |
Bar (non-virtual setter) |
0 bytes |
A Perfetto heapprofd profile of the real app attributes the unreleased memory to:
monoeg_malloc
mono_metadata_signature_dup_internal
mono_metadata_signature_dup_full
mono_metadata_signature_dup_delegate_invoke_to_target
mono_marshal_get_delegate_invoke_internal
mono_delegate_trampoline
<generated code>
Regression?
Yes: not present in .NET 6/7 Mono, present from .NET 8.
Known Workarounds
Avoid creating open-instance delegates over virtual methods repeatedly (cache them), or make the target methods non-virtual.
Configuration
.NET 10.0.12 Mono runtime, Android arm64. Not specific to Android as far as the code goes. Any Mono target should be affected.
Other information
Real-world impact: sqlite-net-pcl (1.10.196-beta and master) creates one such delegate per mapped column on every query (FastColumnSetter.CreateTypedSetterDelegate → Delegate.CreateDelegate(typeof(Action<ObjectType, ColumnMemberType>), null, setter)), so any app whose entities implement interfaces leaks ~64 bytes per column per query. In our app that is ~100 KB per user action.
Suggested fix: free the copy on the early returns, or only make it on the cache-miss path, e.g.
if (res) {
g_free (target_method_sig);
return res;
}
for both the ctx and callvirt branches (target_method_sig is NULL for the other subtypes, and g_free (NULL) is a no-op).
- Lingua principale
- C#
- Stelle
- 18.4k
- Fork
- 5.7k
- Merge medio
- 2g 19h
- PR unite (30g)
- 649
Preparare l'ambiente
Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di dotnet/runtime
-
Several MemoryExtensions.LastIndexOfAnyExcept overloads incorrectly document "first occurrence"Apertaarea-System.Memory untriaged
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 83/100
dotnet/runtime#136282 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
MemoryExtensions.LastIndexOf ignores a custom comparer for bitwise-equatable element typesForse già presa @aw0lid l’ha presa oggi. Apertaarea-System.Runtime untriaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
dotnet/runtime#136281 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
area-System.IO.Compression untriaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
dotnet/runtime#136215 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
area-CodeGen-coreclr untriaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
dotnet/runtime#135849 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
[wasm] Emscripten clang wrapper fails when the SDK path contains a spaceForse già presa @agocke l’ha presa 14 giorni fa. Apertaarch-wasm area-Infrastructure untriaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
dotnet/runtime#135416 · 2 commenti · 2 reazioni · 2 assegnatari ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di dotnet/runtime
Issue simili
-
dev/release: verify-release-candidate.sh runs 0 Java tests with Debian's MavenForse già presa @kou l’ha presa oggi. ApertaType: bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
apache/arrow-adbc#4947 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
microsoft/fluentui-blazor#5535 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 1-3 ore Idoneità per principianti 74/100
AvaloniaUI/Avalonia#22582 ·
I maintainer di solito rispondono entro 2 giorni
-
:watch: Not Triaged CAC
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
dotnet/AspNetCore.Docs#38001 ·
I maintainer di solito rispondono entro 1 giorno
-
HTTP logging truncates catch-all route parameters in the formatted request pathForse già presa @johnkaram02 l’ha presa oggi. Apertaarea-telemetry untriaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
dotnet/extensions#7883 ·
I maintainer di solito rispondono entro 1 giorno