[mono] Native memory leak on the first invoke of every open-instance delegate to a virtual method
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
调研方向
从 src/mono/mono/metadata/marshal.c 开始,阅读 mono_marshal_get_delegate_invoke_internal,重点关注复制 target_method_sig 后的两个 wrapper cache 命中返回。检查该分配由谁负责,以及在每条路径上如何释放,然后运行相关的 Mono runtime 测试。当反复将 open-instance delegate 用于虚方法不再导致 native 内存增长,并且现有测试通过时,工作即告完成。
由索引模型根据 Issue 内容生成。
描述
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).
- 主要语言
- C#
- 星标
- 18.4k
- 派生
- 5.7k
- 平均合并
- 2 天 18 小时
- 30 天内合并 PR
- 646
环境准备
在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
dotnet/runtime 的其他 Issue
-
Several MemoryExtensions.LastIndexOfAnyExcept overloads incorrectly document "first occurrence"可能已有人在做 @ehsan-raoufi 于 1 天前认领。 未关闭area-System.Memory untriaged
难度 1/5 1 小时以内 新手友好度 83/100
dotnet/runtime#136282 · 1 条评论 ·
维护者通常 1 天内回复
-
MemoryExtensions.LastIndexOf ignores a custom comparer for bitwise-equatable element types可能已有人在做 @aw0lid 于 2 天前认领。 未关闭area-System.Runtime untriaged
难度 2/5 1-3 小时 新手友好度 74/100
dotnet/runtime#136281 · 2 条评论 ·
维护者通常 1 天内回复
-
area-System.IO.Compression untriaged
难度 2/5 1-3 小时 新手友好度 68/100
dotnet/runtime#136215 · 1 条评论 ·
维护者通常 1 天内回复
-
area-CodeGen-coreclr untriaged
难度 2/5 1-3 小时 新手友好度 62/100
dotnet/runtime#135849 · 1 条评论 ·
维护者通常 1 天内回复
-
[wasm] Emscripten clang wrapper fails when the SDK path contains a space可能已有人在做 @agocke 于 15 天前认领。 未关闭arch-wasm area-Infrastructure untriaged
难度 2/5 1-3 小时 新手友好度 72/100
dotnet/runtime#135416 · 2 条评论 · 2 个 reaction · 已指派 2 人 ·
维护者通常 1 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 78/100
unoplatform/uno.check#591 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 75/100
weaviate/csharp-client#378 ·
-
confirmed enhancement
难度 2/5 1-3 小时 新手友好度 68/100
维护者通常 1 天内回复
-
agentic-workflows automated testing
难度 2/5 1-3 小时 新手友好度 85/100
Azure-Samples/Legacy-Modernization-Agents#194 ·
维护者通常 1 天内回复
-
area-encryption area-security p1-recommended technical-debt
难度 2/5 1 小时以内 新手友好度 85/100
维护者通常 1 天内回复