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

Improve support for Assemblies loaded into Collectible AssemblyLoadContexts #73

Closed
Tobriand opened this issue Feb 4, 2020 · 4 comments
Assignees

Comments

@Tobriand
Copy link

Tobriand commented Feb 4, 2020

As of .Net Core 3.0, it is possible to set custom AssemblyLoadContexts to be collectible, which should enable associated Types to be unloaded when the Unload method is called on that context.

There are two behaviours Stashbox currently has that mean that calling Unload is ineffective when the types from such an AssemblyLoadContext have been loaded into a StashboxContainer or a ResolutionScope:

  1. When disposing of the container, the underlying trees of resolutions are not currently cleared. Given this, there will be both instances of types from the assemblies that need to be unloaded, and potentially generic types pertaining to them (which when it comes to unloading an assembly are just as bad as the instances for preventing memory associated with them from being cleaned up)
  2. There is a (private static) cache in Stashbox.Entity.MetaInformation.MetaRepository that retains (I think) all types that Stashbox has encountered.

For my immediate use case, the below method addresses the issue by nulling all fields within the container, and re-initializing the MetaRepository to the value used in the Stashbox source. However, it would be great if something similar could take place when simply disposing of the StashboxContainer. Obviously because MetaRespository is shared, I'm also not completely certain that invalidating it in this manner is safe.

private void DisposeOfContainerThoroughly()
        {
            if (_Container != null)
            {
                var publicAndPrivateInstanceFields = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic;
                var fields = _Container.GetType().GetFields(publicAndPrivateInstanceFields);
                foreach (var field in fields)
                {
                    if (field.Name == "disposed")
                        continue;
                    var defaultValue = ReflectionHelpers.GetDefault(field.FieldType);
                    field.SetValue(_Container, defaultValue);
                }
            }
            var sbAssemblyName = typeof(StashboxContainer).Assembly.FullName;
            var tmetaInfo = Type.GetType("Stashbox.Entity.MetaInformation, " + sbAssemblyName);
            var fStaticCache = tmetaInfo.GetField("MetaRepository", BindingFlags.Static | BindingFlags.NonPublic);
            var fEmpty = fStaticCache.FieldType.GetField("Empty", BindingFlags.Static | BindingFlags.Public);
            var emptyValue = fEmpty.GetValue(null);
            fStaticCache.SetValue(null, emptyValue);
            _Container = null;
        }
@z4kn4fein
Copy link
Owner

z4kn4fein commented Feb 6, 2020

Hey, thanks for reporting this! Yeah, that shared type cache is a pretty old concept and it looks like this is the time for a rethink. Also, you are right about the registration tree is not cleared properly on disposing of the container, I'm going to refactor these in the next version.

@z4kn4fein z4kn4fein self-assigned this Feb 6, 2020
@z4kn4fein z4kn4fein added this to the v2.9.0 milestone Feb 6, 2020
@z4kn4fein
Copy link
Owner

z4kn4fein commented Apr 4, 2020

Hey, sorry for the late reaction!
I did a bigger refactor in the new version (it's just a prerelease yet) and fixed this issue by removing that static MetaInformation completely, I also did some tests and it seems the collectible assembly load contexts are unloading properly now. Could you please check and confirm that this is true in your case as well? Thanks!

@Tobriand
Copy link
Author

Hi - again sorry for looking into this late. I'm having trouble consuming the more recent version since I've got some functionality that relied on IResolutionScope.GetScopedInstanceOrDefault() (and also some functionality that leant on the extensions APIs to control disposal order, but I believe that is more easily side-stepped).

I'll have a look and see if behavior no longer requires the kind of approach I was taking, though, once I get me code to compile enough.

@z4kn4fein z4kn4fein modified the milestones: v3.1.0, v3.1.1 Jun 9, 2020
@z4kn4fein
Copy link
Owner

Closing due to inactivity, feel free to reopen this, or create a new issue when something related comes up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants