📜 ⬆️ ⬇️

About Garbage Collector, Unity and weak links

I have in my project a certain IT interface and a factory type method

 interface IT {} public IT CreateT(IA a, IB b, IC c, Type concreteType) { //  ,     concreteType,      . } 


Classes that implement IT have constructors with different signatures that accept some combination of objects of types IA , IB or IC , so as the number of IT implementations IT , their creation code started to smell more and more, and finally, it was decided to throw it away simple code with Unity, something like this:
')
 private static IT CreateITInternal(IA a, Type targetType) { using (UnityContainer cont = new UnityContainer()) { cont.RegisterInstance<IA>(a, new ExternallyControlledLifetimeManager()); cont.RegisterType(typeof(IT), targetType, new ExternallyControlledLifetimeManager()); return cont.Resolve<IT>(); } } 

(for simplicity, I will leave only references to IA , so as not to clutter up the code. We need Unity here only to facilitate instantiation of objects and we don’t want to give it control over the lifetime of any objects, so ExternallyControlledLifetimeManager used)
The code is written, tests are written, everything is green, everything works, roll out to production. And then it began ...

The calling code looked like this:

 private static IT CreateIT() { var a = new A(); Type concreteType = typeof(T); return CreateITInternal(a, concreteType); } static void Main(string[] args) { for (int i = 0; i < 1000; ++i) { CreateIT(); } } 

classes and interfaces
 public interface IA { }; public interface IT { }; public class A : IA { } public class T : IT { public T(IA a) { } } 


It seems to be okay, both debug and release work fine in Visual Studio. But if you go and run the .exe file, it stably falls with this exception:

 Unhandled Exception: Microsoft.Practices.Unity.ResolutionFailedException: Resolution of the dependency failed, type = "WeakRefTest.IT", name = "(none)". Exception occurred while: while resolving. Exception is: InvalidOperationException - The current type, WeakRefTest.IA, is an interface and cannot be constructed. Are you missing a type mapping? ----------------------------------------------- At the time of the exception, the container was: Resolving WeakRefTest.T,(none) (mapped from WeakRefTest.IT, (none)) Resolving parameter "a" of constructor WeakRefTest.T(WeakRefTest.IA a) Resolving WeakRefTest.IA,(none) ---> System.InvalidOperationException: The current type, WeakRefTest.IA, is an interface and cannot be constructed. Are you missing a type mapping? 

Stack trace
  at Microsoft.Practices.ObjectBuilder2.DynamicMethodConstructorStrategy.ThrowForAttemptingToConstructInterface(IBuilderContext context) at lambda_method(Closure , IBuilderContext ) at Microsoft.Practices.ObjectBuilder2.DynamicBuildPlanGenerationContext.<>c__DisplayClass1.<GetBuildMethod>b__0(IBuilderContext context) at Microsoft.Practices.ObjectBuilder2.DynamicMethodBuildPlan.BuildUp(IBuilderContext context) at Microsoft.Practices.ObjectBuilder2.BuildPlanStrategy.PreBuildUp(IBuilderContext context) at Microsoft.Practices.ObjectBuilder2.StrategyChain.ExecuteBuildUp(IBuilderContext context) at Microsoft.Practices.ObjectBuilder2.BuilderContext.NewBuildUp(NamedTypeBuildKey newBuildKey) at Microsoft.Practices.Unity.ObjectBuilder.NamedTypeDependencyResolverPolicy.Resolve(IBuilderContext context) at lambda_method(Closure , IBuilderContext ) at Microsoft.Practices.ObjectBuilder2.DynamicBuildPlanGenerationContext.<>c__DisplayClass1.<GetBuildMethod>b__0(IBuilderContext context) at Microsoft.Practices.ObjectBuilder2.DynamicMethodBuildPlan.BuildUp(IBuilderContext context) at Microsoft.Practices.ObjectBuilder2.BuildPlanStrategy.PreBuildUp(IBuilderContext context) at Microsoft.Practices.ObjectBuilder2.StrategyChain.ExecuteBuildUp(IBuilderContext context) at Microsoft.Practices.Unity.UnityContainer.DoBuildUp(Type t, Object existing, String name, IEnumerable`1 resolverOverrides) --- End of inner exception stack trace --- at Microsoft.Practices.Unity.UnityContainer.DoBuildUp(Type t, Object existing, String name, IEnumerable`1 resolverOverrides) at Microsoft.Practices.Unity.UnityContainer.Resolve(Type t, String name, ResolverOverride[] resolverOverrides) at Microsoft.Practices.Unity.UnityContainerExtensions.Resolve[T](IUnityContainer container, ResolverOverride[] overrides) at WeakRefTest.Program.CreateITInternal(IA a, Type targetType) at WeakRefTest.Program.CreateIT() at WeakRefTest.Program.Main(String[] args) 



wat? We literally two lines above added an object to the container ...

Covering all sorts of logs showed that everything works, how it should work. Objects are actually registered in the container, and everything falls far from the first iteration.

A brief re-reading of the documentation gave the suspect: ExternallyControlledLifetimeManager , inside it keeps weak references to objects placed in the container, and it is possible if between placing an object of type IA in a container and a request for constructing an object of type IT it will be destroyed, then the design should fall just like that the exception. But on the other hand, an object of type IA can only be removed if the weak link from our container is the only one, but in our case this is not the case! There are strong references to this object in CreateITInternal and CreateIT on the stack, which should extend its life at least until exiting CreateIT . Or not? Checking:

  private static IT CreateITInternal(IA a, Type targetType) { using (UnityContainer cont = new UnityContainer()) { cont.RegisterInstance<IA>(a, new ExternallyControlledLifetimeManager()); cont.RegisterType(typeof(IT), targetType, new ExternallyControlledLifetimeManager()); GC.Collect(); return cont.Resolve<IT>(); } } 


Now falls on the first iteration. Those. the case is really garbage collection and weak links.

A simpler example:

  private static void TestWeakRef() { var sa = new A(); var wa = new WeakReference<A>(sa); GC.Collect(); A sa2; wa.TryGetTarget(out sa2); Console.WriteLine("{0}", sa2 == null ? "null" : "not null"); Console.ReadLine(); } 


In Visual Studio, it returns "not null" when launched outside the studio, it returns "null."

Apparently, the presence of strong links in the code does not prolong the lifetime of the object before these links go out of scope, and the real dereferencing of these links matters.

Fix is ​​extremely simple:
  private static IT CreateITInternal(IA a, Type targetType) { using (UnityContainer cont = new UnityContainer()) { cont.RegisterInstance<IA>(a, new ExternallyControlledLifetimeManager()); cont.RegisterType(typeof(IT), targetType, new ExternallyControlledLifetimeManager()); IT ret = cont.Resolve<IT>(); GC.KeepAlive(a); return ret; } } 


So, using weak links, caution the garbage collector may be more aggressive than you expect.

It's still not clear, though, why VS always works when it starts up.

Source: https://habr.com/ru/post/268155/


All Articles