KEMBAR78
DataContractSerializer caches collectible types in statics · Issue #77877 · dotnet/runtime · GitHub
Skip to content

DataContractSerializer caches collectible types in statics #77877

@mburnell

Description

@mburnell

Description

The AssemblyLoadContext of any type that has undergone data contract serialization cannot be unloaded / collected. The specific offender is DataContractCriticalHelper, which has a few static caches and also retains a reference to the most-recently-serialized type.

Reproduction Steps

  1. Create a collectible AssemblyLoadContext
  2. Load a DataContractSerializable type into the collectible ALC
  3. Instantiate the collectible type
  4. Serialise the instance using something like:
var ser = new DataContractSerializer(type);
using (var ms = new System.IO.MemoryStream())
{
    ser.WriteObject(ms, document);
    var bytes = ms.ToArray();
    return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
  1. Invoke Unload() on the collectible ALC
  2. Trigger a garbage collection (collect / finalise / collect)
  3. Review loaded assemblies using a debugger or something like AppDomain.CurrentDomain.GetAssemblies()
  4. Observe that the collectible ALC / assembly has not unloaded.
  5. Repeat steps but skip step 4 and observer that the ALC / assembly has unloaded properly

Expected behavior

Unloading and collecting results in proper collection of collectible types / assemblies / contexts.

Actual behavior

Collectible types / assemblies / contexts that have been data contract serialised cannot be collected.

Regression?

No response

Known Workarounds

The following code allows proper collection to occur:

private static void ClearDataContractCaches()
{
    var t = AppDomain.CurrentDomain.GetAssemblies()
        .SelectMany(x => x.GetTypes())
        .Single(x => x.Name == "DataContractCriticalHelper");

    var staticCache = t.GetFields(BindingFlags.NonPublic | BindingFlags.Static).Select(x => x.GetValue(t)).ToArray();
    
    foreach (var dictionary in staticCache.OfType<IDictionary>())
        dictionary.Clear();

    foreach (var array in staticCache.OfType<Array>())
    {
        for (var i = 0; i < array.Length; I++)
        {
            array.SetValue(null, i);
        }
    }
    
    var handleBreaker = new DataContractSerializer(typeof(object));
    var o = new object();
    using (var ms = new MemoryStream())
    {
        handleBreaker.WriteObject(ms, o);
    }
}

Configuration

.NET 6
macOS Ventura 13.0
ARM64

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions