Есть у меня в проекте некий интерфейс
Классы, реализующие
(для простоты я оставлю только ссылки на
Код написан, тесты написаны, все зелено, все работает, выкатывам в продакшен. И тут началось…
Вызывающий код выглядел примерно так:
Вроде ничего страшного, в Visual Studio прекрасно работает и debug и release. Но если пойти и запустить .exe-файл, он стабильно падает с таким исключением:
wat? Мы же буквально двумя строчками выше добавили объект в контейнер…
Обкладываение всяческими логами показывало, что все работает, как оно должно работать. Объекты действительно регистрируются в контейнере, да и падает все далеко не на первой итерации.
Недолгое перечитывание документации дало подозреваемого:
Теперь падает на первой же итерации. Т.е. дело действительно в сборке мусора и слабых ссылках.
Более простой пример:
В Visual Studio выдает «not null» при запу��ке вне студии выдает «null».
Судя по всему, наличие сильных ссылок в коде не продлевает время жизни объекта до выхода этих ссылок из области видимости, а значение имеет реальное разыменование этих ссылок.
Фикс чрезвычайно прост:
Так что, используя слабые ссылки, бдите, сборщик мусора может оказаться более агрессивным, чем вы ожидаете.
Все еще непонятно, правда, почему при запуске в VS всегда работает.
IT и factory метод типаinterface IT {} public IT CreateT(IA a, IB b, IC c, Type concreteType) { // куча говнокода, который создает объект типа concreteType, определяемого в месте вызова в рантайме. }
Классы, реализующие
IT, имеют конструкторы с разными сигнатурами, прин��мающими какую-то комбинацию объектов типов IA, IB или IC, поэтому с ростом количества реализаций IT, код их создания начинал пахнуть все сильнее, и, наконец, было принято решение его выкинуть и заменить простым кодом с Unity, примерно такого содержания: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>(); } }
(для простоты я оставлю только ссылки на
IA, чтобы не загромождать код. Unity нам тут нужен только для облегчения инстанциирования объектов и мы не хотим давать ему контролировать время жизни никаких объектов, поэтому использован ExternallyControlledLifetimeManager)Код написан, тесты написаны, все зелено, все работает, выкатывам в продакшен. И тут началось…
Вызывающий код выглядел примерно так:
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(); } }
классы и интерфейсы
public interface IA { }; public interface IT { }; public class A : IA { } public class T : IT { public T(IA a) { } }
Вроде ничего страшного, в Visual Studio прекрасно работает и debug и release. Но если пойти и запустить .exe-файл, он стабильно падает с таким исключением:
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? Мы же буквально двумя строчками выше добавили объект в контейнер…
Обкладываение всяческими логами показывало, что все работает, как оно должно работать. Объекты действительно регистрируются в контейнере, да и падает все далеко не на первой итерации.
Недолгое перечитывание документации дало подозреваемого:
ExternallyControlledLifetimeManager, внутри себя он хранит слабые ссылки на объекты, помещаемые в контейнер, и возможно, если между помещением объекта типа IA в контейнер и запросом конструирования объекта типа IT он будет уничтожен, то конструирование должно упасть как раз с подобным исключением. Но с другой стороны, объект типа IA может быть удален только в случае, если слабая ссылка из нашего контейнера является единственной, но в нашем случае это не так! И в CreateITInternal и в CreateIT на стеке есть сильные ссылки на этот объект, что должно продлевать его жизнь как минимум до выхода из CreateIT. Или нет? Проверяем: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>(); } }
Теперь падает на первой же итерации. Т.е. дело действительно в сборке мусора и слабых ссылках.
Более простой пример:
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(); }
В Visual Studio выдает «not null» при запу��ке вне студии выдает «null».
Судя по всему, наличие сильных ссылок в коде не продлевает время жизни объекта до выхода этих ссылок из области видимости, а значение имеет реальное разыменование этих ссылок.
Фикс чрезвычайно прост:
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; } }
Так что, используя слабые ссылки, бдите, сборщик мусора может оказаться более агрессивным, чем вы ожидаете.
Все еще непонятно, правда, почему при запуске в VS всегда работает.
