Есть у меня в проекте некий интерфейс
Классы, реализующие
(для простоты я оставлю только ссылки на
Код написан, тесты написаны, все зелено, все работает, выкатывам в продакшен. И тут началось…
Вызывающий код выглядел примерно так:
Вроде ничего страшного, в 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 всегда работает.