JasperFx / lamar

Fast Inversion of Control Tool and Successor to StructureMap
https://jasperfx.github.io/lamar
MIT License
563 stars 118 forks source link

Instance Hash Collisions for Large Service Registry #398

Open jwoodmanbuildium opened 2 months ago

jwoodmanbuildium commented 2 months ago

When an application has a large service graph (thousands of services), instance hash collisions occur, causing the wrong implementations to be resolved at runtime.

Exception info:

ClassName: System.InvalidCastException
Message: Unable to cast object of type '{ImplentationType}' to type '{ServiceType}'.

Stack Trace:
   at (Closure`6 , Scope )
   at Lamar.IoC.Instances.GeneratedInstance.<>c__DisplayClass5_1.<BuildFuncResolver>b__0(Scope s)
   at Lamar.IoC.Instances.GeneratedInstance.Resolve(Scope scope)
   at (Closure`9 , Scope )
   at Lamar.IoC.Instances.GeneratedInstance.<>c__DisplayClass5_1.<BuildFuncResolver>b__0(Scope s)
   at Lamar.IoC.Instances.GeneratedInstance.Resolve(Scope scope)
   at (ArrayClosure , Scope )
   at Lamar.IoC.Instances.GeneratedInstance.<>c__DisplayClass5_1.<BuildFuncResolver>b__0(Scope s)
   at Lamar.IoC.Instances.GeneratedInstance.Resolve(Scope scope)
   at (ArrayClosure , Scope )
   at Lamar.IoC.Instances.GeneratedInstance.<>c__DisplayClass5_1.<BuildFuncResolver>b__0(Scope s)
   at Lamar.IoC.Instances.GeneratedInstance.Resolve(Scope scope)
   at (ArrayClosure , Scope )
   at Lamar.IoC.Instances.GeneratedInstance.<>c__DisplayClass5_1.<BuildFuncResolver>b__0(Scope s)
   at Lamar.IoC.Instances.GeneratedInstance.Resolve(Scope scope)
   at (ArrayClosure , Scope )
   at Lamar.IoC.Instances.GeneratedInstance.<>c__DisplayClass5_1.<BuildFuncResolver>b__0(Scope s)
   at Lamar.IoC.Instances.GeneratedInstance.Resolve(Scope scope)
   at (Closure`1 , Scope )
   at Lamar.IoC.Instances.GeneratedInstance.<>c__DisplayClass5_1.<BuildFuncResolver>b__0(Scope s)
   at Lamar.IoC.Instances.GeneratedInstance.Resolve(Scope scope)
   at (ArrayClosure , Scope )
   at Lamar.IoC.Instances.GeneratedInstance.<>c__DisplayClass5_1.<BuildFuncResolver>b__0(Scope s)
   at Lamar.IoC.Scope.GetInstance(Type serviceType)
   at Lamar.IoC.Scope.GetInstance[T]()

I've added a workaround to recalculate the hash duplicates at startup by updating the instance names, but it would be nice if these collisions could be avoided by Lamar.

e.g. workaround

void MitigateHashCollisions(ServiceRegistry serviceRegistry)
{
    var hashCollisions = serviceRegistry
        .Select(x => x.ImplementationInstance)
        .OfType<Instance>()
        .GroupBy(x => x.Hash)
        .Where(x => x.Select(y => y.ServiceType).Distinct().Count() > 1);

    foreach (var collisionGroup in hashCollisions)
    {
        foreach (var lamarInstance in collisionGroup.Where(x => !x.IsExplicitlyNamed))
        {
            // Updating the name will result in a new hash
            lamarInstance.Name = $"Default_{lamarInstance.ServiceType.Name}";
        }
    }
}
jeremydmiller commented 1 month ago

I'd be happy for a pull request on this one