Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DataContractSerializer caches collectible types in statics #77877

Closed
mburnell opened this issue Nov 4, 2022 · 1 comment · Fixed by #90437
Closed

DataContractSerializer caches collectible types in statics #77877

mburnell opened this issue Nov 4, 2022 · 1 comment · Fixed by #90437

Comments

@mburnell
Copy link

mburnell commented Nov 4, 2022

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

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Nov 4, 2022
@ghost
Copy link

ghost commented Nov 4, 2022

Tagging subscribers to this area: @dotnet/area-system-collections
See info in area-owners.md if you want to be subscribed.

Issue Details

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

Author: mburnell
Assignees: -
Labels:

area-System.Collections

Milestone: -

@StephenMolloy StephenMolloy added this to the 8.0.0 milestone Feb 2, 2023
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Feb 2, 2023
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Aug 11, 2023
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Aug 14, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Sep 14, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants