diff --git a/src/LightInject.Tests/Issue614.cs b/src/LightInject.Tests/Issue614.cs new file mode 100644 index 00000000..3791d110 --- /dev/null +++ b/src/LightInject.Tests/Issue614.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace LightInject.Tests +{ + public class Issue614 + { + [Fact] + public void ShouldResolveOpenGenericEnumerablesConcurrentlyWithCanGetInstance() + { + var closingTypes = typeof(string).Assembly.GetExportedTypes() + .Where(t => t.IsClass && !t.IsGenericTypeDefinition && !t.ContainsGenericParameters) + .Take(200) + .ToArray(); + + for (int iteration = 0; iteration < 20; iteration++) + { + var container = new ServiceContainer(new ContainerOptions { EnableMicrosoftCompatibility = true, EnableVariance = false }); + container.Register(typeof(IBehavior<>), typeof(FirstBehavior<>)); + container.Register(typeof(IBehavior<>), typeof(SecondBehavior<>)); + container.Register(typeof(Handler<>), typeof(Handler<>)); + + using var barrier = new Barrier(Environment.ProcessorCount * 2); + var tasks = Enumerable.Range(0, Environment.ProcessorCount * 2).Select(taskIndex => Task.Run(() => + { + barrier.SignalAndWait(); + for (int i = taskIndex; i < closingTypes.Length; i += 2) + { + var closingType = closingTypes[i]; + if (taskIndex % 2 == 0) + { + container.CanGetInstance(typeof(IEnumerable<>).MakeGenericType(typeof(IBehavior<>).MakeGenericType(closingType)), string.Empty); + } + else + { + var handler = (IHandler)container.GetInstance(typeof(Handler<>).MakeGenericType(closingType)); + Assert.NotNull(handler); + } + } + })).ToArray(); + + Task.WaitAll(tasks); + } + } + + public interface IBehavior + { + } + + public class FirstBehavior : IBehavior + { + } + + public class SecondBehavior : IBehavior + { + } + + public interface IHandler + { + int BehaviorCount { get; } + } + + public class Handler : IHandler + { + public Handler(IEnumerable> behaviors) + { + BehaviorCount = behaviors.Count(); + } + + public int BehaviorCount { get; } + } + } +} diff --git a/src/LightInject/LightInject.cs b/src/LightInject/LightInject.cs index cba279bf..d8278473 100644 --- a/src/LightInject/LightInject.cs +++ b/src/LightInject/LightInject.cs @@ -2836,13 +2836,18 @@ public IEnumerable AvailableServices /// public bool CanGetInstance(Type serviceType, string serviceName) { - if (serviceType.IsFuncRepresentingService() || serviceType.IsFuncRepresentingNamedService() || serviceType.IsFuncWithParameters() || serviceType.IsLazy()) + // Resolving the emit method might register services (e.g. closed generics), so we need to + // synchronize with CreateDelegate that reads the same registrations. + lock (lockObject) { - var returnType = serviceType.GenericTypeArguments.Last(); - return GetEmitMethod(returnType, serviceName) != null || availableServices.ContainsKey(serviceType); - } + if (serviceType.IsFuncRepresentingService() || serviceType.IsFuncRepresentingNamedService() || serviceType.IsFuncWithParameters() || serviceType.IsLazy()) + { + var returnType = serviceType.GenericTypeArguments.Last(); + return GetEmitMethod(returnType, serviceName) != null || availableServices.ContainsKey(serviceType); + } - return GetEmitMethod(serviceType, serviceName) != null; + return GetEmitMethod(serviceType, serviceName) != null; + } } ///