-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Closed
Labels
Milestone
Description
Description
When an object that implements IReflect
is queried for IDispatch
, the runtime enumerates the object's members and constructs a CCW. This operation seems to create managed arrays that outlive the CCW, and subsequent short-lived CCWs leak increasing amounts of managed memory.
Reproduction Steps
The following program reproduces the leak:
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
while (true)
{
var pDispTest = Marshal.GetIDispatchForObject(new Test());
Marshal.Release(pDispTest);
GC.Collect();
GC.WaitForPendingFinalizers();
}
internal sealed class Test : IReflect
{
private static readonly FieldInfo[] _fields = Enumerable.Range(0, 10).Select(i => (FieldInfo)new Field($"f{i}")).ToArray();
private static readonly MethodInfo[] _methods = Enumerable.Range(0, 10).Select(i => (MethodInfo)new Method($"m{i}")).ToArray();
private static readonly PropertyInfo[] _properties = Enumerable.Range(0, 10).Select(i => (PropertyInfo)new Property($"p{i}")).ToArray();
public FieldInfo[] GetFields(BindingFlags bindingAttr) => _fields;
public MethodInfo[] GetMethods(BindingFlags bindingAttr) => _methods;
public PropertyInfo[] GetProperties(BindingFlags bindingAttr) => _properties;
// all other IReflect members throw NotImplementedException
private sealed class Property : PropertyInfo
{
public Property(string name) => Name = name;
public override string Name { get; }
// all other required overrides throw NotImplementedException
}
private sealed class Field : FieldInfo
{
public Field(string name) => Name = name;
public override string Name { get; }
// all other required overrides throw NotImplementedException
}
private sealed class Method : MethodInfo
{
public Method(string name) => Name = name;
public override string Name { get; }
// all other required overrides throw NotImplementedException
}
}
Expected behavior
No memory leak.
Actual behavior
Snapshots at program start and after 10,000 iterations:
Managed heap diff:
Leaked arrays:
Leaked array contents:
Our best guess: It looks like each CCW adds all reflected member references to a long-lived, ever-growing managed collection that uses arrays under the hood.
Regression?
Unknown.
Known Workarounds
None.
Configuration
- .NET SDK 6.0.401
- Windows 11 21H2
- x64
Other information
None.