Dneska nás tu zabavil problém s chováním Castle Windsor.
Symptom
Na jednom z projektů Castle Windsor „občas“ ignoroval registrace a odmítal resolvovat komponenty, které měly být regulérně zaregistrovány pomocí installerů. Při vývoji a ladění z Visual Studia na vývojářských strojích vše fungovalo, po continous integration deploymentu na STAGE aplikace nejela. Padala na chybu:
System.ApplicationException: Error in resolving dependency XY. ---> Castle.MicroKernel.ComponentNotFoundException: No component for supporting the service XY was found
Celé se to chovalo velmi nedeterministicky. Při re-deploymentu stejného buildu aplikace pomocí XCOPY se tato rozběhla, po iisreset zase nešla, atp.
Analysis
Vzhledem k tomu, že aplikace se chovala jinak na vývojářských strojích (fungovala v DEBUG i RELEASE) a jinak na STAGE, bylo potřeba zvolit trochu agresivnější metody ladění. Pokus o remote-debugging vypadal zdlouhavě, analýza memory dumpu ukázala, že ve WindsorContaineru příslušné registrace opravdu chybí.
Zkoumání registrací/installerů nakonec ukázalo, kde je problém.
Cause
Uspořádání solution bylo jednoduché. Webová aplikace, v Global.asax:Application_Start se vytvoří container a následně se na něm zavolá extension-metoda ConfigureForWebApplication(), která samotná je však v samostatné class-library WindsorInstallers, kde jsou umístěné všechny installery a extension-metoda si je volá přes Install(FromAssembly.This()):
public static void ConfigureForWebApplication(this IWindsorContainer container) { container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel)); container.AddFacility(); container.Install(FromAssembly.This()); }
Kritickým a zároveň podezření-hodným místem je právě řádek s installem. Rychlý náhled do zdrojáků FromAssembly.This() a konzultace s MSDN ohledně Assembly.GetCallingAssembly potvrzuje podezření na inlining a zmatečné určení assembly s installery. Metoda ConfigureForWebApplication() byla inlinována a GetCallingAssembly() tak vracelo jako assembly tu, v které je Global.asax:Application_Start().
Action
Náprava je naštěstí jednoduchá, metodu ConfigureForWebApplication() je potřeba označit atributem MethodIml pro compiler tak, aby inlining neproběhl:
[MethodImpl(MethodImplOptions.NoInlining)] public static void ConfigureForWebApplication(this IWindsorContainer container) { container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel)); container.AddFacility(); container.Install(FromAssembly.This()); }
Alternativně lze samozřejmě zdrojovou assembly určit jiným determinističtějším způsobem (FromAssembly.jinak()).