diff --git a/src/DI/ServiceLookup/CallSiteFactory.cs b/src/DI/ServiceLookup/CallSiteFactory.cs index 878b64b..01d78e5 100644 --- a/src/DI/ServiceLookup/CallSiteFactory.cs +++ b/src/DI/ServiceLookup/CallSiteFactory.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -14,12 +15,16 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { internal class CallSiteFactory { + private const int DefaultSlot = 0; private readonly List _descriptors; - private readonly Dictionary _callSiteCache = new Dictionary(); + private readonly ConcurrentDictionary _callSiteCache = new ConcurrentDictionary(); private readonly Dictionary _descriptorLookup = new Dictionary(); + private readonly StackGuard _stackGuard; + public CallSiteFactory(IEnumerable descriptors) { + _stackGuard = new StackGuard(); _descriptors = descriptors.ToList(); Populate(descriptors); } @@ -66,57 +71,63 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup } } - internal IServiceCallSite CreateCallSite(Type serviceType, CallSiteChain callSiteChain) + internal ServiceCallSite GetCallSite(Type serviceType, CallSiteChain callSiteChain) { - lock (_callSiteCache) - { - if (_callSiteCache.TryGetValue(serviceType, out var cachedCallSite)) - { - return cachedCallSite; - } - - IServiceCallSite callSite; - try - { - callSiteChain.CheckCircularDependency(serviceType); - - callSite = TryCreateExact(serviceType, callSiteChain) ?? - TryCreateOpenGeneric(serviceType, callSiteChain) ?? - TryCreateEnumerable(serviceType, callSiteChain); - } - finally - { - callSiteChain.Remove(serviceType); - } - - _callSiteCache[serviceType] = callSite; - - return callSite; - } +#if NETCOREAPP2_0 + return _callSiteCache.GetOrAdd(serviceType, (type, chain) => CreateCallSite(type, chain), callSiteChain); +#else + return _callSiteCache.GetOrAdd(serviceType, type => CreateCallSite(type, callSiteChain)); +#endif } - private IServiceCallSite TryCreateExact(Type serviceType, CallSiteChain callSiteChain) + private ServiceCallSite CreateCallSite(Type serviceType, CallSiteChain callSiteChain) + { + if (!_stackGuard.TryEnterOnCurrentStack()) + { + return _stackGuard.RunOnEmptyStack((type, chain) => CreateCallSite(type, chain), serviceType, callSiteChain); + } + + ServiceCallSite callSite; + try + { + callSiteChain.CheckCircularDependency(serviceType); + + callSite = TryCreateExact(serviceType, callSiteChain) ?? + TryCreateOpenGeneric(serviceType, callSiteChain) ?? + TryCreateEnumerable(serviceType, callSiteChain); + } + finally + { + callSiteChain.Remove(serviceType); + } + + _callSiteCache[serviceType] = callSite; + + return callSite; + } + + private ServiceCallSite TryCreateExact(Type serviceType, CallSiteChain callSiteChain) { if (_descriptorLookup.TryGetValue(serviceType, out var descriptor)) { - return TryCreateExact(descriptor.Last, serviceType, callSiteChain); + return TryCreateExact(descriptor.Last, serviceType, callSiteChain, DefaultSlot); } return null; } - private IServiceCallSite TryCreateOpenGeneric(Type serviceType, CallSiteChain callSiteChain) + private ServiceCallSite TryCreateOpenGeneric(Type serviceType, CallSiteChain callSiteChain) { if (serviceType.IsConstructedGenericType && _descriptorLookup.TryGetValue(serviceType.GetGenericTypeDefinition(), out var descriptor)) { - return TryCreateOpenGeneric(descriptor.Last, serviceType, callSiteChain); + return TryCreateOpenGeneric(descriptor.Last, serviceType, callSiteChain, DefaultSlot); } return null; } - private IServiceCallSite TryCreateEnumerable(Type serviceType, CallSiteChain callSiteChain) + private ServiceCallSite TryCreateEnumerable(Type serviceType, CallSiteChain callSiteChain) { if (serviceType.IsConstructedGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) @@ -124,7 +135,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup var itemType = serviceType.GenericTypeArguments.Single(); callSiteChain.Add(serviceType); - var callSites = new List(); + var callSites = new List(); // If item type is not generic we can safely use descriptor cache if (!itemType.IsConstructedGenericType && @@ -134,8 +145,10 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { var descriptor = descriptors[i]; + // Last service should get slot 0 + var slot = descriptors.Count - i - 1; // There may not be any open generics here - var callSite = TryCreateExact(descriptor, itemType, callSiteChain); + var callSite = TryCreateExact(descriptor, itemType, callSiteChain, slot); Debug.Assert(callSite != null); callSites.Add(callSite); @@ -143,17 +156,21 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup } else { - foreach (var descriptor in _descriptors) + var slot = 0; + // We are going in reverse so the last service in descriptor list gets slot 0 + for (var i = _descriptors.Count - 1; i >= 0; i--) { - var callSite = TryCreateExact(descriptor, itemType, callSiteChain) ?? - TryCreateOpenGeneric(descriptor, itemType, callSiteChain); - + var descriptor = _descriptors[i]; + var callSite = TryCreateExact(descriptor, itemType, callSiteChain, slot) ?? + TryCreateOpenGeneric(descriptor, itemType, callSiteChain, slot); + slot++; if (callSite != null) { callSites.Add(callSite); } } + callSites.Reverse(); } return new IEnumerableCallSite(itemType, callSites.ToArray()); @@ -162,71 +179,51 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return null; } - private IServiceCallSite TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain) + private ServiceCallSite TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot) { if (serviceType == descriptor.ServiceType) { - IServiceCallSite callSite; + ServiceCallSite callSite; + var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot); if (descriptor.ImplementationInstance != null) { callSite = new ConstantCallSite(descriptor.ServiceType, descriptor.ImplementationInstance); } else if (descriptor.ImplementationFactory != null) { - callSite = new FactoryCallSite(descriptor.ServiceType, descriptor.ImplementationFactory); + callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationFactory); } else if (descriptor.ImplementationType != null) { - callSite = CreateConstructorCallSite(descriptor.ServiceType, descriptor.ImplementationType, callSiteChain); + callSite = CreateConstructorCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationType, callSiteChain); } else { throw new InvalidOperationException("Invalid service descriptor"); } - return ApplyLifetime(callSite, descriptor, descriptor.Lifetime); + return callSite; } return null; } - private IServiceCallSite TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain) + private ServiceCallSite TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot) { if (serviceType.IsConstructedGenericType && serviceType.GetGenericTypeDefinition() == descriptor.ServiceType) { Debug.Assert(descriptor.ImplementationType != null, "descriptor.ImplementationType != null"); - + var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot); var closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments); - var constructorCallSite = CreateConstructorCallSite(serviceType, closedType, callSiteChain); - - return ApplyLifetime(constructorCallSite, Tuple.Create(descriptor, serviceType), descriptor.Lifetime); + return CreateConstructorCallSite(lifetime, serviceType, closedType, callSiteChain); } return null; } - private IServiceCallSite ApplyLifetime(IServiceCallSite serviceCallSite, object cacheKey, ServiceLifetime descriptorLifetime) - { - if (serviceCallSite is ConstantCallSite) - { - return serviceCallSite; - } - - switch (descriptorLifetime) - { - case ServiceLifetime.Transient: - return new TransientCallSite(serviceCallSite); - case ServiceLifetime.Scoped: - return new ScopedCallSite(serviceCallSite, cacheKey); - case ServiceLifetime.Singleton: - return new SingletonCallSite(serviceCallSite, cacheKey); - default: - throw new ArgumentOutOfRangeException(nameof(descriptorLifetime)); - } - } - - private IServiceCallSite CreateConstructorCallSite(Type serviceType, Type implementationType, CallSiteChain callSiteChain) + private ServiceCallSite CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, + CallSiteChain callSiteChain) { callSiteChain.Add(serviceType, implementationType); @@ -235,7 +232,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup .Where(constructor => constructor.IsPublic) .ToArray(); - IServiceCallSite[] parameterCallSites = null; + ServiceCallSite[] parameterCallSites = null; if (constructors.Length == 0) { @@ -247,7 +244,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup var parameters = constructor.GetParameters(); if (parameters.Length == 0) { - return new CreateInstanceCallSite(serviceType, implementationType); + return new ConstructorCallSite(lifetime, serviceType, constructor); } parameterCallSites = CreateArgumentCallSites( @@ -257,7 +254,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup parameters, throwIfCallSiteNotFound: true); - return new ConstructorCallSite(serviceType, constructor, parameterCallSites); + return new ConstructorCallSite(lifetime, serviceType, constructor, parameterCallSites); } Array.Sort(constructors, @@ -316,23 +313,21 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup else { Debug.Assert(parameterCallSites != null); - return parameterCallSites.Length == 0 ? - (IServiceCallSite)new CreateInstanceCallSite(serviceType, implementationType) : - new ConstructorCallSite(serviceType, bestConstructor, parameterCallSites); + return new ConstructorCallSite(lifetime, serviceType, bestConstructor, parameterCallSites); } } - private IServiceCallSite[] CreateArgumentCallSites( + private ServiceCallSite[] CreateArgumentCallSites( Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, bool throwIfCallSiteNotFound) { - var parameterCallSites = new IServiceCallSite[parameters.Length]; + var parameterCallSites = new ServiceCallSite[parameters.Length]; for (var index = 0; index < parameters.Length; index++) { - var callSite = CreateCallSite(parameters[index].ParameterType, callSiteChain); + var callSite = GetCallSite(parameters[index].ParameterType, callSiteChain); if (callSite == null && ParameterDefaultValue.TryGetDefaultValue(parameters[index], out var defaultValue)) { @@ -358,7 +353,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup } - public void Add(Type type, IServiceCallSite serviceCallSite) + public void Add(Type type, ServiceCallSite serviceCallSite) { _callSiteCache[type] = serviceCallSite; } diff --git a/src/DI/ServiceLookup/CallSiteResultCacheLocation.cs b/src/DI/ServiceLookup/CallSiteResultCacheLocation.cs new file mode 100644 index 0000000..e76d5b3 --- /dev/null +++ b/src/DI/ServiceLookup/CallSiteResultCacheLocation.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.Extensions.DependencyInjection.ServiceLookup +{ + internal enum CallSiteResultCacheLocation + { + Root, + Scope, + Dispose, + None + } +} \ No newline at end of file diff --git a/src/DI/ServiceLookup/CallSiteRuntimeResolver.cs b/src/DI/ServiceLookup/CallSiteRuntimeResolver.cs index 52b780c..7c0f7ca 100644 --- a/src/DI/ServiceLookup/CallSiteRuntimeResolver.cs +++ b/src/DI/ServiceLookup/CallSiteRuntimeResolver.cs @@ -1,27 +1,38 @@ using System; using System.Runtime.ExceptionServices; +using System.Threading; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - internal class CallSiteRuntimeResolver : CallSiteVisitor + internal sealed class CallSiteRuntimeResolver : CallSiteVisitor { - public object Resolve(IServiceCallSite callSite, ServiceProviderEngineScope scope) + public object Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope) { - return VisitCallSite(callSite, scope); - } - - protected override object VisitTransient(TransientCallSite transientCallSite, ServiceProviderEngineScope scope) - { - return scope.CaptureDisposable( - VisitCallSite(transientCallSite.ServiceCallSite, scope)); - } - - protected override object VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope) - { - object[] parameterValues = new object[constructorCallSite.ParameterCallSites.Length]; - for (var index = 0; index < parameterValues.Length; index++) + return VisitCallSite(callSite, new RuntimeResolverContext { - parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], scope); + Scope = scope + }); + } + + protected override object VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context) + { + return context.Scope.CaptureDisposable(VisitCallSiteMain(transientCallSite, context)); + } + + protected override object VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) + { + object[] parameterValues; + if (constructorCallSite.ParameterCallSites.Length == 0) + { + parameterValues = Array.Empty(); + } + else + { + parameterValues = new object[constructorCallSite.ParameterCallSites.Length]; + for (var index = 0; index < parameterValues.Length; index++) + { + parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], context); + } } try @@ -36,55 +47,77 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup } } - protected override object VisitSingleton(SingletonCallSite singletonCallSite, ServiceProviderEngineScope scope) + protected override object VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) { - return VisitScoped(singletonCallSite, scope.Engine.Root); + return VisitCache(singletonCallSite, context, context.Scope.Engine.Root, RuntimeResolverLock.Root); } - protected override object VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope) + protected override object VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) { - lock (scope.ResolvedServices) + // Check if we are in the situation where scoped service was promoted to singleton + // and we need to lock the root + var requiredScope = context.Scope == context.Scope.Engine.Root ? + RuntimeResolverLock.Root : + RuntimeResolverLock.Scope; + + return VisitCache(singletonCallSite, context, context.Scope, requiredScope); + } + + private object VisitCache(ServiceCallSite scopedCallSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) + { + bool lockTaken = false; + var resolvedServices = serviceProviderEngine.ResolvedServices; + + // Taking locks only once allows us to fork resolution process + // on another thread without causing the deadlock because we + // always know that we are going to wait the other thread to finish before + // releasing the lock + if ((context.AcquiredLocks & lockType) == 0) { - if (!scope.ResolvedServices.TryGetValue(scopedCallSite.CacheKey, out var resolved)) + Monitor.Enter(resolvedServices, ref lockTaken); + } + + try + { + if (!resolvedServices.TryGetValue(scopedCallSite.Cache.Key, out var resolved)) { - resolved = VisitCallSite(scopedCallSite.ServiceCallSite, scope); - scope.CaptureDisposable(resolved); - scope.ResolvedServices.Add(scopedCallSite.CacheKey, resolved); + resolved = VisitCallSiteMain(scopedCallSite, new RuntimeResolverContext + { + Scope = serviceProviderEngine, + AcquiredLocks = context.AcquiredLocks | lockType + }); + + serviceProviderEngine.CaptureDisposable(resolved); + resolvedServices.Add(scopedCallSite.Cache.Key, resolved); } + return resolved; } + finally + { + if (lockTaken) + { + Monitor.Exit(resolvedServices); + } + } } - protected override object VisitConstant(ConstantCallSite constantCallSite, ServiceProviderEngineScope scope) + protected override object VisitConstant(ConstantCallSite constantCallSite, RuntimeResolverContext context) { return constantCallSite.DefaultValue; } - protected override object VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, ServiceProviderEngineScope scope) + protected override object VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, RuntimeResolverContext context) { - try - { - return Activator.CreateInstance(createInstanceCallSite.ImplementationType); - } - catch (Exception ex) when (ex.InnerException != null) - { - ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); - // The above line will always throw, but the compiler requires we throw explicitly. - throw; - } + return context.Scope; } - protected override object VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, ServiceProviderEngineScope scope) + protected override object VisitServiceScopeFactory(ServiceScopeFactoryCallSite serviceScopeFactoryCallSite, RuntimeResolverContext context) { - return scope; + return context.Scope.Engine; } - protected override object VisitServiceScopeFactory(ServiceScopeFactoryCallSite serviceScopeFactoryCallSite, ServiceProviderEngineScope scope) - { - return scope.Engine; - } - - protected override object VisitIEnumerable(IEnumerableCallSite enumerableCallSite, ServiceProviderEngineScope scope) + protected override object VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context) { var array = Array.CreateInstance( enumerableCallSite.ItemType, @@ -92,15 +125,29 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup for (var index = 0; index < enumerableCallSite.ServiceCallSites.Length; index++) { - var value = VisitCallSite(enumerableCallSite.ServiceCallSites[index], scope); + var value = VisitCallSite(enumerableCallSite.ServiceCallSites[index], context); array.SetValue(value, index); } return array; } - protected override object VisitFactory(FactoryCallSite factoryCallSite, ServiceProviderEngineScope scope) + protected override object VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context) { - return factoryCallSite.Factory(scope); + return factoryCallSite.Factory(context.Scope); } } + + internal struct RuntimeResolverContext + { + public ServiceProviderEngineScope Scope { get; set; } + + public RuntimeResolverLock AcquiredLocks { get; set; } + } + + [Flags] + internal enum RuntimeResolverLock + { + Scope = 1, + Root = 2 + } } \ No newline at end of file diff --git a/src/DI/ServiceLookup/CallSiteValidator.cs b/src/DI/ServiceLookup/CallSiteValidator.cs index ed06475..42ba65f 100644 --- a/src/DI/ServiceLookup/CallSiteValidator.cs +++ b/src/DI/ServiceLookup/CallSiteValidator.cs @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup // Keys are services being resolved via GetService, values - first scoped service in their call site tree private readonly ConcurrentDictionary _scopedServices = new ConcurrentDictionary(); - public void ValidateCallSite(IServiceCallSite callSite) + public void ValidateCallSite(ServiceCallSite callSite) { var scoped = VisitCallSite(callSite, default); if (scoped != null) @@ -40,11 +40,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup } } - protected override Type VisitTransient(TransientCallSite transientCallSite, CallSiteValidatorState state) - { - return VisitCallSite(transientCallSite.ServiceCallSite, state); - } - protected override Type VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state) { Type result = null; @@ -74,16 +69,16 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return result; } - protected override Type VisitSingleton(SingletonCallSite singletonCallSite, CallSiteValidatorState state) + protected override Type VisitRootCache(ServiceCallSite singletonCallSite, CallSiteValidatorState state) { state.Singleton = singletonCallSite; - return VisitCallSite(singletonCallSite.ServiceCallSite, state); + return VisitCallSiteMain(singletonCallSite, state); } - protected override Type VisitScoped(ScopedCallSite scopedCallSite, CallSiteValidatorState state) + protected override Type VisitScopeCache(ServiceCallSite scopedCallSite, CallSiteValidatorState state) { // We are fine with having ServiceScopeService requested by singletons - if (scopedCallSite.ServiceCallSite is ServiceScopeFactoryCallSite) + if (scopedCallSite is ServiceScopeFactoryCallSite) { return null; } @@ -97,14 +92,12 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup )); } - VisitCallSite(scopedCallSite.ServiceCallSite, state); + VisitCallSiteMain(scopedCallSite, state); return scopedCallSite.ServiceType; } protected override Type VisitConstant(ConstantCallSite constantCallSite, CallSiteValidatorState state) => null; - protected override Type VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, CallSiteValidatorState state) => null; - protected override Type VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, CallSiteValidatorState state) => null; protected override Type VisitServiceScopeFactory(ServiceScopeFactoryCallSite serviceScopeFactoryCallSite, CallSiteValidatorState state) => null; @@ -113,7 +106,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup internal struct CallSiteValidatorState { - public SingletonCallSite Singleton { get; set; } + public ServiceCallSite Singleton { get; set; } } } } \ No newline at end of file diff --git a/src/DI/ServiceLookup/CallSiteVisitor.cs b/src/DI/ServiceLookup/CallSiteVisitor.cs index 9d5a4c7..6f884b6 100644 --- a/src/DI/ServiceLookup/CallSiteVisitor.cs +++ b/src/DI/ServiceLookup/CallSiteVisitor.cs @@ -1,10 +1,40 @@ using System; +using System.Runtime.CompilerServices; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { internal abstract class CallSiteVisitor { - protected virtual TResult VisitCallSite(IServiceCallSite callSite, TArgument argument) + private readonly StackGuard _stackGuard; + + protected CallSiteVisitor() + { + _stackGuard = new StackGuard(); + } + + protected virtual TResult VisitCallSite(ServiceCallSite callSite, TArgument argument) + { + if (!_stackGuard.TryEnterOnCurrentStack()) + { + return _stackGuard.RunOnEmptyStack((c, a) => VisitCallSite(c, a), callSite, argument); + } + + switch (callSite.Cache.Location) + { + case CallSiteResultCacheLocation.Root: + return VisitRootCache(callSite, argument); + case CallSiteResultCacheLocation.Scope: + return VisitScopeCache(callSite, argument); + case CallSiteResultCacheLocation.Dispose: + return VisitDisposeCache(callSite, argument); + case CallSiteResultCacheLocation.None: + return VisitNoCache(callSite, argument); + default: + throw new ArgumentOutOfRangeException(); + } + } + + protected virtual TResult VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) { switch (callSite.Kind) { @@ -14,16 +44,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return VisitIEnumerable((IEnumerableCallSite)callSite, argument); case CallSiteKind.Constructor: return VisitConstructor((ConstructorCallSite)callSite, argument); - case CallSiteKind.Transient: - return VisitTransient((TransientCallSite)callSite, argument); - case CallSiteKind.Singleton: - return VisitSingleton((SingletonCallSite)callSite, argument); - case CallSiteKind.Scope: - return VisitScoped((ScopedCallSite)callSite, argument); case CallSiteKind.Constant: return VisitConstant((ConstantCallSite)callSite, argument); - case CallSiteKind.CreateInstance: - return VisitCreateInstance((CreateInstanceCallSite)callSite, argument); case CallSiteKind.ServiceProvider: return VisitServiceProvider((ServiceProviderCallSite)callSite, argument); case CallSiteKind.ServiceScopeFactory: @@ -33,18 +55,30 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup } } - protected abstract TResult VisitTransient(TransientCallSite transientCallSite, TArgument argument); + protected virtual TResult VisitNoCache(ServiceCallSite callSite, TArgument argument) + { + return VisitCallSiteMain(callSite, argument); + } + + protected virtual TResult VisitDisposeCache(ServiceCallSite callSite, TArgument argument) + { + return VisitCallSiteMain(callSite, argument); + } + + protected virtual TResult VisitRootCache(ServiceCallSite callSite, TArgument argument) + { + return VisitCallSiteMain(callSite, argument); + } + + protected virtual TResult VisitScopeCache(ServiceCallSite callSite, TArgument argument) + { + return VisitCallSiteMain(callSite, argument); + } protected abstract TResult VisitConstructor(ConstructorCallSite constructorCallSite, TArgument argument); - protected abstract TResult VisitSingleton(SingletonCallSite singletonCallSite, TArgument argument); - - protected abstract TResult VisitScoped(ScopedCallSite scopedCallSite, TArgument argument); - protected abstract TResult VisitConstant(ConstantCallSite constantCallSite, TArgument argument); - protected abstract TResult VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, TArgument argument); - protected abstract TResult VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, TArgument argument); protected abstract TResult VisitServiceScopeFactory(ServiceScopeFactoryCallSite serviceScopeFactoryCallSite, TArgument argument); diff --git a/src/DI/ServiceLookup/CompiledServiceProviderEngine.cs b/src/DI/ServiceLookup/CompiledServiceProviderEngine.cs index 76773ca..3f76a16 100644 --- a/src/DI/ServiceLookup/CompiledServiceProviderEngine.cs +++ b/src/DI/ServiceLookup/CompiledServiceProviderEngine.cs @@ -15,7 +15,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup ExpressionResolverBuilder = new ExpressionResolverBuilder(RuntimeResolver, this, Root); } - protected override Func RealizeService(IServiceCallSite callSite) + protected override Func RealizeService(ServiceCallSite callSite) { var realizedService = ExpressionResolverBuilder.Build(callSite); RealizedServices[callSite.ServiceType] = realizedService; diff --git a/src/DI/ServiceLookup/ConstantCallSite.cs b/src/DI/ServiceLookup/ConstantCallSite.cs index e547fc6..35346b6 100644 --- a/src/DI/ServiceLookup/ConstantCallSite.cs +++ b/src/DI/ServiceLookup/ConstantCallSite.cs @@ -5,17 +5,17 @@ using System; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - internal class ConstantCallSite : IServiceCallSite + internal class ConstantCallSite : ServiceCallSite { internal object DefaultValue { get; } - public ConstantCallSite(Type serviceType, object defaultValue) + public ConstantCallSite(Type serviceType, object defaultValue): base(ResultCache.None) { DefaultValue = defaultValue; } - public Type ServiceType => DefaultValue.GetType(); - public Type ImplementationType => DefaultValue.GetType(); - public CallSiteKind Kind { get; } = CallSiteKind.Constant; + public override Type ServiceType => DefaultValue.GetType(); + public override Type ImplementationType => DefaultValue.GetType(); + public override CallSiteKind Kind { get; } = CallSiteKind.Constant; } } diff --git a/src/DI/ServiceLookup/ConstructorCallSite.cs b/src/DI/ServiceLookup/ConstructorCallSite.cs index 28e4806..eff7a30 100644 --- a/src/DI/ServiceLookup/ConstructorCallSite.cs +++ b/src/DI/ServiceLookup/ConstructorCallSite.cs @@ -6,21 +6,25 @@ using System.Reflection; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - internal class ConstructorCallSite : IServiceCallSite + internal class ConstructorCallSite : ServiceCallSite { internal ConstructorInfo ConstructorInfo { get; } - internal IServiceCallSite[] ParameterCallSites { get; } + internal ServiceCallSite[] ParameterCallSites { get; } - public ConstructorCallSite(Type serviceType, ConstructorInfo constructorInfo, IServiceCallSite[] parameterCallSites) + public ConstructorCallSite(ResultCache cache, Type serviceType, ConstructorInfo constructorInfo) : this(cache, serviceType, constructorInfo, Array.Empty()) + { + } + + public ConstructorCallSite(ResultCache cache, Type serviceType, ConstructorInfo constructorInfo, ServiceCallSite[] parameterCallSites) : base(cache) { ServiceType = serviceType; ConstructorInfo = constructorInfo; ParameterCallSites = parameterCallSites; } - public Type ServiceType { get; } + public override Type ServiceType { get; } - public Type ImplementationType => ConstructorInfo.DeclaringType; - public CallSiteKind Kind { get; } = CallSiteKind.Constructor; + public override Type ImplementationType => ConstructorInfo.DeclaringType; + public override CallSiteKind Kind { get; } = CallSiteKind.Constructor; } } diff --git a/src/DI/ServiceLookup/CreateInstanceCallSite.cs b/src/DI/ServiceLookup/CreateInstanceCallSite.cs index 4007203..188ee90 100644 --- a/src/DI/ServiceLookup/CreateInstanceCallSite.cs +++ b/src/DI/ServiceLookup/CreateInstanceCallSite.cs @@ -6,17 +6,4 @@ using System.Runtime.ExceptionServices; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - internal class CreateInstanceCallSite : IServiceCallSite - { - public Type ServiceType { get; } - - public Type ImplementationType { get; } - public CallSiteKind Kind { get; } = CallSiteKind.CreateInstance; - - public CreateInstanceCallSite(Type serviceType, Type implementationType) - { - ServiceType = serviceType; - ImplementationType = implementationType; - } - } } diff --git a/src/DI/ServiceLookup/DynamicServiceProviderEngine.cs b/src/DI/ServiceLookup/DynamicServiceProviderEngine.cs index 08b933c..8b05fa3 100644 --- a/src/DI/ServiceLookup/DynamicServiceProviderEngine.cs +++ b/src/DI/ServiceLookup/DynamicServiceProviderEngine.cs @@ -14,7 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { } - protected override Func RealizeService(IServiceCallSite callSite) + protected override Func RealizeService(ServiceCallSite callSite) { var callCount = 0; return scope => diff --git a/src/DI/ServiceLookup/Expressions/ExpressionResolverBuilder.cs b/src/DI/ServiceLookup/Expressions/ExpressionResolverBuilder.cs index 5d9df40..6d1433e 100644 --- a/src/DI/ServiceLookup/Expressions/ExpressionResolverBuilder.cs +++ b/src/DI/ServiceLookup/Expressions/ExpressionResolverBuilder.cs @@ -14,18 +14,18 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { internal static readonly MethodInfo InvokeFactoryMethodInfo = GetMethodInfo, IServiceProvider>>((a, b) => a.Invoke(b)); internal static readonly MethodInfo CaptureDisposableMethodInfo = GetMethodInfo>((a, b) => a.CaptureDisposable(b)); - internal static readonly MethodInfo TryGetValueMethodInfo = GetMethodInfo, object, object, bool>>((a, b, c) => a.TryGetValue(b, out c)); - internal static readonly MethodInfo AddMethodInfo = GetMethodInfo, object, object>>((a, b, c) => a.Add(b, c)); + internal static readonly MethodInfo TryGetValueMethodInfo = GetMethodInfo, ServiceCacheKey, object, bool>>((a, b, c) => a.TryGetValue(b, out c)); + internal static readonly MethodInfo AddMethodInfo = GetMethodInfo, ServiceCacheKey, object>>((a, b, c) => a.Add(b, c)); internal static readonly MethodInfo MonitorEnterMethodInfo = GetMethodInfo>((lockObj, lockTaken) => Monitor.Enter(lockObj, ref lockTaken)); internal static readonly MethodInfo MonitorExitMethodInfo = GetMethodInfo>(lockObj => Monitor.Exit(lockObj)); internal static readonly MethodInfo CallSiteRuntimeResolverResolve = - GetMethodInfo>((r, c, p) => r.Resolve(c, p)); + GetMethodInfo>((r, c, p) => r.Resolve(c, p)); internal static readonly MethodInfo ArrayEmptyMethodInfo = typeof(Array).GetMethod(nameof(Array.Empty)); private static readonly ParameterExpression ScopeParameter = Expression.Parameter(typeof(ServiceProviderEngineScope)); - private static readonly ParameterExpression ResolvedServices = Expression.Variable(typeof(IDictionary), ScopeParameter.Name + "resolvedServices"); + private static readonly ParameterExpression ResolvedServices = Expression.Variable(typeof(IDictionary), ScopeParameter.Name + "resolvedServices"); private static readonly BinaryExpression ResolvedServicesVariableAssignment = Expression.Assign(ResolvedServices, Expression.Property(ScopeParameter, nameof(ServiceProviderEngineScope.ResolvedServices))); @@ -41,7 +41,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup private readonly ServiceProviderEngineScope _rootScope; - public ExpressionResolverBuilder(CallSiteRuntimeResolver runtimeResolver, IServiceScopeFactory serviceScopeFactory, ServiceProviderEngineScope rootScope) + public ExpressionResolverBuilder(CallSiteRuntimeResolver runtimeResolver, IServiceScopeFactory serviceScopeFactory, ServiceProviderEngineScope rootScope): + base() { if (runtimeResolver == null) { @@ -52,13 +53,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup _rootScope = rootScope; } - public Func Build(IServiceCallSite callSite) + public Func Build(ServiceCallSite callSite) { - if (callSite is SingletonCallSite singletonCallSite) + if (callSite.Cache.Location == CallSiteResultCacheLocation.Root) { // If root call site is singleton we can return Func calling // _runtimeResolver.Resolve directly and avoid Expression generation - if (TryResolveSingletonValue(singletonCallSite, out var value)) + if (TryResolveSingletonValue(callSite, out var value)) { return scope => value; } @@ -69,15 +70,15 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return BuildExpression(callSite).Compile(); } - private bool TryResolveSingletonValue(SingletonCallSite singletonCallSite, out object value) + private bool TryResolveSingletonValue(ServiceCallSite singletonCallSite, out object value) { lock (_rootScope.ResolvedServices) { - return _rootScope.ResolvedServices.TryGetValue(singletonCallSite.CacheKey, out value); + return _rootScope.ResolvedServices.TryGetValue(singletonCallSite.Cache.Key, out value); } } - private Expression> BuildExpression(IServiceCallSite callSite) + private Expression> BuildExpression(ServiceCallSite callSite) { var context = new CallSiteExpressionBuilderContext { @@ -99,7 +100,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return Expression.Lambda>(serviceExpression, ScopeParameter); } - protected override Expression VisitSingleton(SingletonCallSite singletonCallSite, CallSiteExpressionBuilderContext context) + protected override Expression VisitRootCache(ServiceCallSite singletonCallSite, CallSiteExpressionBuilderContext context) { if (TryResolveSingletonValue(singletonCallSite, out var value)) { @@ -109,7 +110,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return Expression.Call( Expression.Constant(_runtimeResolver), CallSiteRuntimeResolverResolve, - Expression.Constant(singletonCallSite, typeof(IServiceCallSite)), + Expression.Constant(singletonCallSite, typeof(ServiceCallSite)), context.ScopeParameter); } @@ -118,11 +119,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return Expression.Constant(constantCallSite.DefaultValue); } - protected override Expression VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, CallSiteExpressionBuilderContext context) - { - return Expression.New(createInstanceCallSite.ImplementationType); - } - protected override Expression VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, CallSiteExpressionBuilderContext context) { return context.ScopeParameter; @@ -155,14 +151,14 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup callSite.ItemType))); } - protected override Expression VisitTransient(TransientCallSite callSite, CallSiteExpressionBuilderContext context) + protected override Expression VisitDisposeCache(ServiceCallSite callSite, CallSiteExpressionBuilderContext context) { - var implType = callSite.ServiceCallSite.ImplementationType; + var implType = callSite.ImplementationType; // Elide calls to GetCaptureDisposable if the implementation type isn't disposable return TryCaptureDisposible( implType, context.ScopeParameter, - VisitCallSite(callSite.ServiceCallSite, context)); + VisitCallSiteMain(callSite, context)); } private Expression TryCaptureDisposible(Type implType, ParameterExpression scope, Expression service) @@ -180,10 +176,18 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup protected override Expression VisitConstructor(ConstructorCallSite callSite, CallSiteExpressionBuilderContext context) { var parameters = callSite.ConstructorInfo.GetParameters(); - var parameterExpressions = new Expression[callSite.ParameterCallSites.Length]; - for (int i = 0; i < parameterExpressions.Length; i++) + Expression[] parameterExpressions; + if (callSite.ParameterCallSites.Length == 0) { - parameterExpressions[i] = Convert(VisitCallSite(callSite.ParameterCallSites[i], context), parameters[i].ParameterType); + parameterExpressions = Array.Empty(); + } + else + { + parameterExpressions = new Expression[callSite.ParameterCallSites.Length]; + for (int i = 0; i < parameterExpressions.Length; i++) + { + parameterExpressions[i] = Convert(VisitCallSite(callSite.ParameterCallSites[i], context), parameters[i].ParameterType); + } } return Expression.New(callSite.ConstructorInfo, parameterExpressions); } @@ -199,17 +203,17 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return Expression.Convert(expression, type); } - protected override Expression VisitScoped(ScopedCallSite callSite, CallSiteExpressionBuilderContext context) + protected override Expression VisitScopeCache(ServiceCallSite callSite, CallSiteExpressionBuilderContext context) { - return BuildScopedExpression(callSite, context, VisitCallSite(callSite.ServiceCallSite, context)); + return BuildScopedExpression(callSite, context, VisitCallSiteMain(callSite, context)); } // Move off the main stack - private Expression BuildScopedExpression(ScopedCallSite callSite, CallSiteExpressionBuilderContext context, Expression service) + private Expression BuildScopedExpression(ServiceCallSite callSite, CallSiteExpressionBuilderContext context, Expression service) { var keyExpression = Expression.Constant( - callSite.CacheKey, - typeof(object)); + callSite.Cache.Key, + typeof(ServiceCacheKey)); var resolvedVariable = Expression.Variable(typeof(object), "resolved"); diff --git a/src/DI/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs b/src/DI/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs index f7b55a4..2e4507f 100644 --- a/src/DI/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs +++ b/src/DI/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup _expressionResolverBuilder = new ExpressionResolverBuilder(RuntimeResolver, this, Root); } - protected override Func RealizeService(IServiceCallSite callSite) + protected override Func RealizeService(ServiceCallSite callSite) { var realizedService = _expressionResolverBuilder.Build(callSite); RealizedServices[callSite.ServiceType] = realizedService; diff --git a/src/DI/ServiceLookup/FactoryCallSite.cs b/src/DI/ServiceLookup/FactoryCallSite.cs index 548607d..dea2dab 100644 --- a/src/DI/ServiceLookup/FactoryCallSite.cs +++ b/src/DI/ServiceLookup/FactoryCallSite.cs @@ -5,19 +5,19 @@ using System; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - internal class FactoryCallSite : IServiceCallSite + internal class FactoryCallSite : ServiceCallSite { public Func Factory { get; } - public FactoryCallSite(Type serviceType, Func factory) + public FactoryCallSite(ResultCache cache, Type serviceType, Func factory) : base(cache) { Factory = factory; ServiceType = serviceType; } - public Type ServiceType { get; } - public Type ImplementationType => null; + public override Type ServiceType { get; } + public override Type ImplementationType => null; - public CallSiteKind Kind { get; } = CallSiteKind.Factory; + public override CallSiteKind Kind { get; } = CallSiteKind.Factory; } } diff --git a/src/DI/ServiceLookup/IEnumerableCallSite.cs b/src/DI/ServiceLookup/IEnumerableCallSite.cs index bbfb081..8749aa8 100644 --- a/src/DI/ServiceLookup/IEnumerableCallSite.cs +++ b/src/DI/ServiceLookup/IEnumerableCallSite.cs @@ -6,19 +6,19 @@ using System.Collections.Generic; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - internal class IEnumerableCallSite : IServiceCallSite + internal class IEnumerableCallSite : ServiceCallSite { internal Type ItemType { get; } - internal IServiceCallSite[] ServiceCallSites { get; } + internal ServiceCallSite[] ServiceCallSites { get; } - public IEnumerableCallSite(Type itemType, IServiceCallSite[] serviceCallSites) + public IEnumerableCallSite(Type itemType, ServiceCallSite[] serviceCallSites) : base(ResultCache.None) { ItemType = itemType; ServiceCallSites = serviceCallSites; } - public Type ServiceType => typeof(IEnumerable<>).MakeGenericType(ItemType); - public Type ImplementationType => ItemType.MakeArrayType(); - public CallSiteKind Kind { get; } = CallSiteKind.IEnumerable; + public override Type ServiceType => typeof(IEnumerable<>).MakeGenericType(ItemType); + public override Type ImplementationType => ItemType.MakeArrayType(); + public override CallSiteKind Kind { get; } = CallSiteKind.IEnumerable; } } \ No newline at end of file diff --git a/src/DI/ServiceLookup/ILEmit/ILEmitCallSiteAnalyzer.cs b/src/DI/ServiceLookup/ILEmit/ILEmitCallSiteAnalyzer.cs index 25d4899..db39d45 100644 --- a/src/DI/ServiceLookup/ILEmit/ILEmitCallSiteAnalyzer.cs +++ b/src/DI/ServiceLookup/ILEmit/ILEmitCallSiteAnalyzer.cs @@ -20,7 +20,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup internal static ILEmitCallSiteAnalyzer Instance { get; } = new ILEmitCallSiteAnalyzer(); - protected override ILEmitCallSiteAnalysisResult VisitTransient(TransientCallSite transientCallSite, object argument) => VisitCallSite(transientCallSite.ServiceCallSite, argument); + protected override ILEmitCallSiteAnalysisResult VisitDisposeCache(ServiceCallSite transientCallSite, object argument) => VisitCallSiteMain(transientCallSite, argument); protected override ILEmitCallSiteAnalysisResult VisitConstructor(ConstructorCallSite constructorCallSite, object argument) { @@ -32,17 +32,15 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return result; } - protected override ILEmitCallSiteAnalysisResult VisitSingleton(SingletonCallSite singletonCallSite, object argument) => VisitCallSite(singletonCallSite.ServiceCallSite, argument); + protected override ILEmitCallSiteAnalysisResult VisitRootCache(ServiceCallSite singletonCallSite, object argument) => VisitCallSiteMain(singletonCallSite, argument); - protected override ILEmitCallSiteAnalysisResult VisitScoped(ScopedCallSite scopedCallSite, object argument) + protected override ILEmitCallSiteAnalysisResult VisitScopeCache(ServiceCallSite scopedCallSite, object argument) { - return new ILEmitCallSiteAnalysisResult(ScopedILSize, hasScope: true).Add(VisitCallSite(scopedCallSite.ServiceCallSite, argument)); + return new ILEmitCallSiteAnalysisResult(ScopedILSize, hasScope: true).Add(VisitCallSiteMain(scopedCallSite, argument)); } protected override ILEmitCallSiteAnalysisResult VisitConstant(ConstantCallSite constantCallSite, object argument) => new ILEmitCallSiteAnalysisResult(ConstantILSize); - protected override ILEmitCallSiteAnalysisResult VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, object argument) => new ILEmitCallSiteAnalysisResult(ConstructorILSize); - protected override ILEmitCallSiteAnalysisResult VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, object argument) => new ILEmitCallSiteAnalysisResult(ServiceProviderSize); protected override ILEmitCallSiteAnalysisResult VisitServiceScopeFactory(ServiceScopeFactoryCallSite serviceScopeFactoryCallSite, object argument) => new ILEmitCallSiteAnalysisResult(ConstantILSize); @@ -59,6 +57,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup protected override ILEmitCallSiteAnalysisResult VisitFactory(FactoryCallSite factoryCallSite, object argument) => new ILEmitCallSiteAnalysisResult(FactoryILSize); - public ILEmitCallSiteAnalysisResult CollectGenerationInfo(IServiceCallSite callSite) => VisitCallSite(callSite, null); + public ILEmitCallSiteAnalysisResult CollectGenerationInfo(ServiceCallSite callSite) => VisitCallSite(callSite, null); } } \ No newline at end of file diff --git a/src/DI/ServiceLookup/ILEmit/ILEmitResolverBuilder.cs b/src/DI/ServiceLookup/ILEmit/ILEmitResolverBuilder.cs index b826f5d..652e8c9 100644 --- a/src/DI/ServiceLookup/ILEmit/ILEmitResolverBuilder.cs +++ b/src/DI/ServiceLookup/ILEmit/ILEmitResolverBuilder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; @@ -19,6 +20,9 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup private static readonly FieldInfo RootField = typeof(ILEmitResolverBuilderRuntimeContext).GetField(nameof(ILEmitResolverBuilderRuntimeContext.Root)); private static readonly FieldInfo FactoriesField = typeof(ILEmitResolverBuilderRuntimeContext).GetField(nameof(ILEmitResolverBuilderRuntimeContext.Factories)); private static readonly FieldInfo ConstantsField = typeof(ILEmitResolverBuilderRuntimeContext).GetField(nameof(ILEmitResolverBuilderRuntimeContext.Constants)); + private static readonly MethodInfo GetTypeFromHandleMethod = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)); + + private static readonly ConstructorInfo CacheKeyCtor = typeof(ServiceCacheKey).GetConstructors().First(); private class ILEmitResolverBuilderRuntimeContext { @@ -35,7 +39,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup private readonly ServiceProviderEngineScope _rootScope; - public ILEmitResolverBuilder(CallSiteRuntimeResolver runtimeResolver, IServiceScopeFactory serviceScopeFactory, ServiceProviderEngineScope rootScope) + public ILEmitResolverBuilder(CallSiteRuntimeResolver runtimeResolver, IServiceScopeFactory serviceScopeFactory, ServiceProviderEngineScope rootScope) : + base() { if (runtimeResolver == null) { @@ -46,13 +51,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup _rootScope = rootScope; } - public Func Build(IServiceCallSite callSite) + public Func Build(ServiceCallSite callSite) { - if (callSite is SingletonCallSite singletonCallSite) + if (callSite.Cache.Location == CallSiteResultCacheLocation.Root) { // If root call site is singleton we can return Func calling // _runtimeResolver.Resolve directly and avoid Expression generation - if (TryResolveSingletonValue(singletonCallSite, out var value)) + if (TryResolveSingletonValue(callSite, out var value)) { return scope => value; } @@ -63,12 +68,12 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return BuildType(callSite); } - protected override Expression VisitTransient(TransientCallSite transientCallSite, ILEmitResolverBuilderContext argument) + protected override Expression VisitDisposeCache(ServiceCallSite transientCallSite, ILEmitResolverBuilderContext argument) { // RuntimeScope.CaptureDisposables([create value]) - var shouldCapture = BeginCaptureDisposable(transientCallSite.ServiceCallSite.ImplementationType, argument); + var shouldCapture = BeginCaptureDisposable(transientCallSite.ImplementationType, argument); - VisitCallSite(transientCallSite.ServiceCallSite, argument); + VisitCallSiteMain(transientCallSite, argument); if (shouldCapture) { @@ -88,9 +93,9 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return null; } - protected override Expression VisitSingleton(SingletonCallSite singletonCallSite, ILEmitResolverBuilderContext argument) + protected override Expression VisitRootCache(ServiceCallSite callSite, ILEmitResolverBuilderContext argument) { - if (TryResolveSingletonValue(singletonCallSite, out var value)) + if (TryResolveSingletonValue(callSite, out var value)) { AddConstant(argument, value); return null; @@ -101,7 +106,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup argument.Generator.Emit(OpCodes.Ldarg_0); argument.Generator.Emit(OpCodes.Ldfld, RuntimeResolverField); - AddConstant(argument, singletonCallSite); + AddConstant(argument, callSite); argument.Generator.Emit(OpCodes.Ldarg_0); argument.Generator.Emit(OpCodes.Ldfld, RootField); @@ -110,7 +115,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return null; } - protected override Expression VisitScoped(ScopedCallSite scopedCallSite, ILEmitResolverBuilderContext argument) + protected override Expression VisitScopeCache(ServiceCallSite scopedCallSite, ILEmitResolverBuilderContext argument) { // var cacheKey = scopedCallSite.CacheKey; @@ -125,13 +130,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup // } var resultLocal = argument.Generator.DeclareLocal(scopedCallSite.ServiceType); - var cacheKeyLocal = argument.Generator.DeclareLocal(typeof(object)); + var cacheKeyLocal = argument.Generator.DeclareLocal(typeof(ServiceCacheKey)); var endLabel = argument.Generator.DefineLabel(); // Resolved services would be 0 local argument.Generator.Emit(OpCodes.Ldloc_0); - AddConstant(argument, scopedCallSite.CacheKey); + AddCacheKey(argument, scopedCallSite.Cache.Key); // Duplicate cache key argument.Generator.Emit(OpCodes.Dup); // and store to local @@ -145,9 +150,9 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup // Jump to create new if nothing in cache argument.Generator.Emit(OpCodes.Brtrue, endLabel); - var shouldCapture = BeginCaptureDisposable(scopedCallSite.ServiceCallSite.ImplementationType, argument); + var shouldCapture = BeginCaptureDisposable(scopedCallSite.ImplementationType, argument); - VisitCallSite(scopedCallSite.ServiceCallSite, argument); + VisitCallSiteMain(scopedCallSite, argument); if (shouldCapture) { @@ -178,13 +183,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup return null; } - protected override Expression VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, ILEmitResolverBuilderContext argument) - { - // new Type - argument.Generator.Emit(OpCodes.Newobj, createInstanceCallSite.ImplementationType.GetConstructor(Type.EmptyTypes)); - return null; - } - protected override Expression VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, ILEmitResolverBuilderContext argument) { // [return] ProviderScope @@ -269,8 +267,17 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup argument.Constants.Add(value); } + private void AddCacheKey(ILEmitResolverBuilderContext argument, ServiceCacheKey key) + { + // new ServiceCacheKey(typeof(key.Type), key.Slot) + argument.Generator.Emit(OpCodes.Ldtoken, key.Type); + argument.Generator.Emit(OpCodes.Call, GetTypeFromHandleMethod); + argument.Generator.Emit(OpCodes.Ldc_I4, key.Slot); + argument.Generator.Emit(OpCodes.Newobj, CacheKeyCtor); + } - private Func BuildType(IServiceCallSite callSite) + + private Func BuildType(ServiceCallSite callSite) { // We need to skip visibility checks because services/constructors might be private var dynamicMethod = new DynamicMethod("ResolveService", @@ -298,13 +305,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup GenerateMethodBody(callSite, method.GetILGenerator(), info); type.CreateTypeInfo(); - assembly.Save(assemblyName+".dll"); + assembly.Save(assemblyName + ".dll"); #endif return (Func)dynamicMethod.CreateDelegate(typeof(Func), runtimeContext); } - private ILEmitResolverBuilderRuntimeContext GenerateMethodBody(IServiceCallSite callSite, ILGenerator generator, ILEmitCallSiteAnalysisResult info) + private ILEmitResolverBuilderRuntimeContext GenerateMethodBody(ServiceCallSite callSite, ILGenerator generator, ILEmitCallSiteAnalysisResult info) { var context = new ILEmitResolverBuilderContext() { @@ -384,11 +391,11 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup }; } - private bool TryResolveSingletonValue(SingletonCallSite singletonCallSite, out object value) + private bool TryResolveSingletonValue(ServiceCallSite callSite, out object value) { lock (_rootScope.ResolvedServices) { - return _rootScope.ResolvedServices.TryGetValue(singletonCallSite.CacheKey, out value); + return _rootScope.ResolvedServices.TryGetValue(callSite.Cache.Key, out value); } } diff --git a/src/DI/ServiceLookup/ILEmit/ILEmitServiceProviderEngine.cs b/src/DI/ServiceLookup/ILEmit/ILEmitServiceProviderEngine.cs index e0a888b..d382e57 100644 --- a/src/DI/ServiceLookup/ILEmit/ILEmitServiceProviderEngine.cs +++ b/src/DI/ServiceLookup/ILEmit/ILEmitServiceProviderEngine.cs @@ -14,7 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup _expressionResolverBuilder = new ILEmitResolverBuilder(RuntimeResolver, this, Root); } - protected override Func RealizeService(IServiceCallSite callSite) + protected override Func RealizeService(ServiceCallSite callSite) { var realizedService = _expressionResolverBuilder.Build(callSite); RealizedServices[callSite.ServiceType] = realizedService; diff --git a/src/DI/ServiceLookup/IServiceProviderEngineCallback.cs b/src/DI/ServiceLookup/IServiceProviderEngineCallback.cs index f30f1ff..e441048 100644 --- a/src/DI/ServiceLookup/IServiceProviderEngineCallback.cs +++ b/src/DI/ServiceLookup/IServiceProviderEngineCallback.cs @@ -7,7 +7,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { internal interface IServiceProviderEngineCallback { - void OnCreate(IServiceCallSite callSite); + void OnCreate(ServiceCallSite callSite); void OnResolve(Type serviceType, IServiceScope scope); } } \ No newline at end of file diff --git a/src/DI/ServiceLookup/ResultCache.cs b/src/DI/ServiceLookup/ResultCache.cs new file mode 100644 index 0000000..16135ba --- /dev/null +++ b/src/DI/ServiceLookup/ResultCache.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; + +namespace Microsoft.Extensions.DependencyInjection.ServiceLookup +{ + internal struct ResultCache + { + public static ResultCache None { get; } = new ResultCache(CallSiteResultCacheLocation.None, ServiceCacheKey.Empty); + + internal ResultCache(CallSiteResultCacheLocation lifetime, ServiceCacheKey cacheKey) + { + Location = lifetime; + Key = cacheKey; + } + + public ResultCache(ServiceLifetime lifetime, Type type, int slot) + { + Debug.Assert(lifetime == ServiceLifetime.Transient || type != null); + + switch (lifetime) + { + case ServiceLifetime.Singleton: + Location = CallSiteResultCacheLocation.Root; + break; + case ServiceLifetime.Scoped: + Location = CallSiteResultCacheLocation.Scope; + break; + case ServiceLifetime.Transient: + Location = CallSiteResultCacheLocation.Dispose; + break; + default: + Location = CallSiteResultCacheLocation.None; + break; + } + Key = new ServiceCacheKey(type, slot); + } + + public CallSiteResultCacheLocation Location { get; set; } + + public ServiceCacheKey Key { get; set; } + } +} \ No newline at end of file diff --git a/src/DI/ServiceLookup/RuntimeServiceProviderEngine.cs b/src/DI/ServiceLookup/RuntimeServiceProviderEngine.cs index d6b7c9d..88c5637 100644 --- a/src/DI/ServiceLookup/RuntimeServiceProviderEngine.cs +++ b/src/DI/ServiceLookup/RuntimeServiceProviderEngine.cs @@ -12,7 +12,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { } - protected override Func RealizeService(IServiceCallSite callSite) + protected override Func RealizeService(ServiceCallSite callSite) { return scope => { diff --git a/src/DI/ServiceLookup/ScopedCallSite.cs b/src/DI/ServiceLookup/ScopedCallSite.cs deleted file mode 100644 index ef92d63..0000000 --- a/src/DI/ServiceLookup/ScopedCallSite.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.Extensions.DependencyInjection.ServiceLookup -{ - internal class ScopedCallSite : IServiceCallSite - { - internal IServiceCallSite ServiceCallSite { get; } - public object CacheKey { get; } - - public ScopedCallSite(IServiceCallSite serviceCallSite, object cacheKey) - { - ServiceCallSite = serviceCallSite; - CacheKey = cacheKey; - } - - public Type ServiceType => ServiceCallSite.ServiceType; - public Type ImplementationType => ServiceCallSite.ImplementationType; - public virtual CallSiteKind Kind { get; } = CallSiteKind.Scope; - } -} \ No newline at end of file diff --git a/src/DI/ServiceLookup/ServiceCacheKey.cs b/src/DI/ServiceLookup/ServiceCacheKey.cs new file mode 100644 index 0000000..2a81e7b --- /dev/null +++ b/src/DI/ServiceLookup/ServiceCacheKey.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Extensions.DependencyInjection.ServiceLookup +{ + internal struct ServiceCacheKey: IEquatable + { + public static ServiceCacheKey Empty { get; } = new ServiceCacheKey(null, 0); + + /// + /// Type of service being cached + /// + public Type Type { get; } + + /// + /// Reverse index of the service when resolved in IEnumerable<Type> where default instance gets slot 0. + /// For example for service collection + /// IService Impl1 + /// IService Impl2 + /// IService Impl3 + /// We would get the following cache keys: + /// Impl1 2 + /// Impl2 1 + /// Impl3 0 + /// + public int Slot { get; } + + public ServiceCacheKey(Type type, int slot) + { + Type = type; + Slot = slot; + } + + public bool Equals(ServiceCacheKey other) + { + return Type == other.Type && Slot == other.Slot; + } + + public override int GetHashCode() + { + unchecked + { + return (Type.GetHashCode() * 397) ^ Slot; + } + } + } +} \ No newline at end of file diff --git a/src/DI/ServiceLookup/IServiceCallSite.cs b/src/DI/ServiceLookup/ServiceCallSite.cs similarity index 50% rename from src/DI/ServiceLookup/IServiceCallSite.cs rename to src/DI/ServiceLookup/ServiceCallSite.cs index e72e415..be1d471 100644 --- a/src/DI/ServiceLookup/IServiceCallSite.cs +++ b/src/DI/ServiceLookup/ServiceCallSite.cs @@ -8,10 +8,16 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup /// /// Summary description for IServiceCallSite /// - internal interface IServiceCallSite + internal abstract class ServiceCallSite { - Type ServiceType { get; } - Type ImplementationType { get; } - CallSiteKind Kind { get; } + protected ServiceCallSite(ResultCache cache) + { + Cache = cache; + } + + public abstract Type ServiceType { get; } + public abstract Type ImplementationType { get; } + public abstract CallSiteKind Kind { get; } + public ResultCache Cache { get; } } } \ No newline at end of file diff --git a/src/DI/ServiceLookup/ServiceProviderCallSite.cs b/src/DI/ServiceLookup/ServiceProviderCallSite.cs index 5062877..b8148f3 100644 --- a/src/DI/ServiceLookup/ServiceProviderCallSite.cs +++ b/src/DI/ServiceLookup/ServiceProviderCallSite.cs @@ -5,10 +5,14 @@ using System; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - internal class ServiceProviderCallSite : IServiceCallSite + internal class ServiceProviderCallSite : ServiceCallSite { - public Type ServiceType { get; } = typeof(IServiceProvider); - public Type ImplementationType { get; } = typeof(ServiceProvider); - public CallSiteKind Kind { get; } = CallSiteKind.ServiceProvider; + public ServiceProviderCallSite() : base(ResultCache.None) + { + } + + public override Type ServiceType { get; } = typeof(IServiceProvider); + public override Type ImplementationType { get; } = typeof(ServiceProvider); + public override CallSiteKind Kind { get; } = CallSiteKind.ServiceProvider; } } diff --git a/src/DI/ServiceLookup/ServiceProviderEngine.cs b/src/DI/ServiceLookup/ServiceProviderEngine.cs index d42c0b1..fcc765c 100644 --- a/src/DI/ServiceLookup/ServiceProviderEngine.cs +++ b/src/DI/ServiceLookup/ServiceProviderEngine.cs @@ -39,7 +39,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup public object GetService(Type serviceType) => GetService(serviceType, Root); - protected abstract Func RealizeService(IServiceCallSite callSite); + protected abstract Func RealizeService(ServiceCallSite callSite); public void Dispose() { @@ -71,7 +71,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup private Func CreateServiceAccessor(Type serviceType) { - var callSite = CallSiteFactory.CreateCallSite(serviceType, new CallSiteChain()); + var callSite = CallSiteFactory.GetCallSite(serviceType, new CallSiteChain()); if (callSite != null) { _callback?.OnCreate(callSite); diff --git a/src/DI/ServiceLookup/ServiceProviderEngineScope.cs b/src/DI/ServiceLookup/ServiceProviderEngineScope.cs index 5b876cd..fcc5b17 100644 --- a/src/DI/ServiceLookup/ServiceProviderEngineScope.cs +++ b/src/DI/ServiceLookup/ServiceProviderEngineScope.cs @@ -20,7 +20,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup Engine = engine; } - internal Dictionary ResolvedServices { get; } = new Dictionary(); + internal Dictionary ResolvedServices { get; } = new Dictionary(); public ServiceProviderEngine Engine { get; } diff --git a/src/DI/ServiceLookup/ServiceScopeFactoryCallSite.cs b/src/DI/ServiceLookup/ServiceScopeFactoryCallSite.cs index 381440a..20e416b 100644 --- a/src/DI/ServiceLookup/ServiceScopeFactoryCallSite.cs +++ b/src/DI/ServiceLookup/ServiceScopeFactoryCallSite.cs @@ -5,10 +5,14 @@ using System; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - internal class ServiceScopeFactoryCallSite : IServiceCallSite + internal class ServiceScopeFactoryCallSite : ServiceCallSite { - public Type ServiceType { get; } = typeof(IServiceScopeFactory); - public Type ImplementationType { get; } = typeof(ServiceProviderEngine); - public CallSiteKind Kind { get; } = CallSiteKind.ServiceScopeFactory; + public ServiceScopeFactoryCallSite() : base(ResultCache.None) + { + } + + public override Type ServiceType { get; } = typeof(IServiceScopeFactory); + public override Type ImplementationType { get; } = typeof(ServiceProviderEngine); + public override CallSiteKind Kind { get; } = CallSiteKind.ServiceScopeFactory; } } diff --git a/src/DI/ServiceLookup/SingletonCallSite.cs b/src/DI/ServiceLookup/SingletonCallSite.cs index dc902d8..b533ba4 100644 --- a/src/DI/ServiceLookup/SingletonCallSite.cs +++ b/src/DI/ServiceLookup/SingletonCallSite.cs @@ -3,12 +3,4 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - internal class SingletonCallSite : ScopedCallSite - { - public SingletonCallSite(IServiceCallSite serviceCallSite, object cacheKey) : base(serviceCallSite, cacheKey) - { - } - - public override CallSiteKind Kind { get; } = CallSiteKind.Singleton; - } } \ No newline at end of file diff --git a/src/DI/ServiceLookup/StackGuard.cs b/src/DI/ServiceLookup/StackGuard.cs new file mode 100644 index 0000000..a297c0d --- /dev/null +++ b/src/DI/ServiceLookup/StackGuard.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.DependencyInjection.ServiceLookup +{ + internal sealed class StackGuard + { + private const int MaxExecutionStackCount = 1024; + + private int _executionStackCount; + + + public bool TryEnterOnCurrentStack() + { +#if NETCOREAPP2_0 + if (RuntimeHelpers.TryEnsureSufficientExecutionStack()) + { + return true; + } +#else + try + { + RuntimeHelpers.EnsureSufficientExecutionStack(); + return true; + } + catch (InsufficientExecutionStackException) + { + } +#endif + + if (_executionStackCount < MaxExecutionStackCount) + { + return false; + } + + throw new InsufficientExecutionStackException(); + } + + public TR RunOnEmptyStack(Func action, T1 arg1, T2 arg2) + { + return RunOnEmptyStackCore(s => + { + var t = (Tuple, T1, T2>)s; + return t.Item1(t.Item2, t.Item3); + }, Tuple.Create(action, arg1, arg2)); + } + + private R RunOnEmptyStackCore(Func action, object state) + { + _executionStackCount++; + + try + { + // Using default scheduler rather than picking up the current scheduler. + Task task = Task.Factory.StartNew(action, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + + TaskAwaiter awaiter = task.GetAwaiter(); + + // Avoid AsyncWaitHandle lazy allocation of ManualResetEvent in the rare case we finish quickly. + if (!awaiter.IsCompleted) + { + // Task.Wait has the potential of inlining the task's execution on the current thread; avoid this. + ((IAsyncResult)task).AsyncWaitHandle.WaitOne(); + } + + // Using awaiter here to unwrap AggregateException. + return awaiter.GetResult(); + } + finally + { + _executionStackCount--; + } + } + } +} diff --git a/src/DI/ServiceLookup/TransientCallSite.cs b/src/DI/ServiceLookup/TransientCallSite.cs deleted file mode 100644 index 905fd9e..0000000 --- a/src/DI/ServiceLookup/TransientCallSite.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.Extensions.DependencyInjection.ServiceLookup -{ - internal class TransientCallSite : IServiceCallSite - { - internal IServiceCallSite ServiceCallSite { get; } - - public TransientCallSite(IServiceCallSite serviceCallSite) - { - ServiceCallSite = serviceCallSite; - } - - public Type ServiceType => ServiceCallSite.ServiceType; - public Type ImplementationType => ServiceCallSite.ImplementationType; - public CallSiteKind Kind { get; } = CallSiteKind.Transient; - } -} \ No newline at end of file diff --git a/src/DI/ServiceProvider.cs b/src/DI/ServiceProvider.cs index 690835b..586089b 100644 --- a/src/DI/ServiceProvider.cs +++ b/src/DI/ServiceProvider.cs @@ -55,7 +55,7 @@ namespace Microsoft.Extensions.DependencyInjection /// public void Dispose() => _engine.Dispose(); - void IServiceProviderEngineCallback.OnCreate(IServiceCallSite callSite) + void IServiceProviderEngineCallback.OnCreate(ServiceCallSite callSite) { _callSiteValidator.ValidateCallSite(callSite); } diff --git a/test/DI.Tests/CallSiteTests.cs b/test/DI.Tests/CallSiteTests.cs index db3cc52..8988017 100644 --- a/test/DI.Tests/CallSiteTests.cs +++ b/test/DI.Tests/CallSiteTests.cs @@ -83,8 +83,8 @@ namespace Microsoft.Extensions.DependencyInjection.Tests { var provider = new DynamicServiceProviderEngine(descriptors, null); - var callSite = provider.CallSiteFactory.CreateCallSite(serviceType, new CallSiteChain()); - var collectionCallSite = provider.CallSiteFactory.CreateCallSite(typeof(IEnumerable<>).MakeGenericType(serviceType), new CallSiteChain()); + var callSite = provider.CallSiteFactory.GetCallSite(serviceType, new CallSiteChain()); + var collectionCallSite = provider.CallSiteFactory.GetCallSite(typeof(IEnumerable<>).MakeGenericType(serviceType), new CallSiteChain()); var compiledCallSite = CompileCallSite(callSite, provider); var compiledCollectionCallSite = CompileCallSite(collectionCallSite, provider); @@ -111,7 +111,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests descriptors.AddScoped(); var provider = new DynamicServiceProviderEngine(descriptors, null); - var callSite = provider.CallSiteFactory.CreateCallSite(typeof(ServiceC), new CallSiteChain()); + var callSite = provider.CallSiteFactory.GetCallSite(typeof(ServiceC), new CallSiteChain()); var compiledCallSite = CompileCallSite(callSite, provider); var serviceC = (ServiceC)compiledCallSite(provider.Root); @@ -137,7 +137,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests { disposables.Add(obj); }; - var callSite = provider.CallSiteFactory.CreateCallSite(typeof(ServiceC), new CallSiteChain()); + var callSite = provider.CallSiteFactory.GetCallSite(typeof(ServiceC), new CallSiteChain()); var compiledCallSite = CompileCallSite(callSite, provider); var serviceC = (DisposableServiceC)compiledCallSite(provider.Root); @@ -163,7 +163,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests { disposables.Add(obj); }; - var callSite = provider.CallSiteFactory.CreateCallSite(typeof(ServiceC), new CallSiteChain()); + var callSite = provider.CallSiteFactory.GetCallSite(typeof(ServiceC), new CallSiteChain()); var compiledCallSite = CompileCallSite(callSite, provider); var serviceC = (DisposableServiceC)compiledCallSite(provider.Root); @@ -192,7 +192,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests { disposables.Add(obj); }; - var callSite = provider.CallSiteFactory.CreateCallSite(typeof(ServiceC), new CallSiteChain()); + var callSite = provider.CallSiteFactory.GetCallSite(typeof(ServiceC), new CallSiteChain()); var compiledCallSite = CompileCallSite(callSite, provider); var serviceC = (ServiceC)compiledCallSite(provider.Root); @@ -217,7 +217,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests { disposables.Add(obj); }; - var callSite = provider.CallSiteFactory.CreateCallSite(typeof(ServiceD), new CallSiteChain()); + var callSite = provider.CallSiteFactory.GetCallSite(typeof(ServiceD), new CallSiteChain()); var compiledCallSite = CompileCallSite(callSite, provider); var serviceD = (ServiceD)compiledCallSite(provider.Root); @@ -235,10 +235,10 @@ namespace Microsoft.Extensions.DependencyInjection.Tests var provider = new DynamicServiceProviderEngine(descriptors, null); - var callSite1 = provider.CallSiteFactory.CreateCallSite(typeof(ClassWithThrowingEmptyCtor), new CallSiteChain()); + var callSite1 = provider.CallSiteFactory.GetCallSite(typeof(ClassWithThrowingEmptyCtor), new CallSiteChain()); var compiledCallSite1 = CompileCallSite(callSite1, provider); - var callSite2 = provider.CallSiteFactory.CreateCallSite(typeof(ClassWithThrowingCtor), new CallSiteChain()); + var callSite2 = provider.CallSiteFactory.GetCallSite(typeof(ClassWithThrowingCtor), new CallSiteChain()); var compiledCallSite2 = CompileCallSite(callSite2, provider); var ex1 = Assert.Throws(() => compiledCallSite1(provider.Root)); @@ -311,12 +311,12 @@ namespace Microsoft.Extensions.DependencyInjection.Tests } } - private static object Invoke(IServiceCallSite callSite, ServiceProviderEngine provider) + private static object Invoke(ServiceCallSite callSite, ServiceProviderEngine provider) { return CallSiteRuntimeResolver.Resolve(callSite, provider.Root); } - private static Func CompileCallSite(IServiceCallSite callSite, ServiceProviderEngine engine) + private static Func CompileCallSite(ServiceCallSite callSite, ServiceProviderEngine engine) { return new ExpressionResolverBuilder(CallSiteRuntimeResolver, engine, engine.Root).Build(callSite); } diff --git a/test/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs b/test/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs index 72566be..ac54104 100644 --- a/test/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs +++ b/test/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs @@ -42,8 +42,9 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup var callSite = callSiteFactory(type); // Assert - var transientCall = Assert.IsType(callSite); - Assert.IsType(transientCall.ServiceCallSite); + Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location); + var ctroCallSite = Assert.IsType(callSite); + Assert.Empty(ctroCallSite.ParameterCallSites); } [Theory] @@ -65,8 +66,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup var callSite = callSiteFactory(type); // Assert - var transientCall = Assert.IsType(callSite); - var constructorCallSite = Assert.IsType(transientCall.ServiceCallSite); + Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location); + var constructorCallSite = Assert.IsType(callSite); Assert.Equal(new[] { typeof(IFakeService) }, GetParameters(constructorCallSite)); } @@ -86,8 +87,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup var callSite = callSiteFactory(type); // Assert - var transientCall = Assert.IsType(callSite); - var constructorCallSite = Assert.IsType(transientCall.ServiceCallSite); + Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location); + var constructorCallSite = Assert.IsType(callSite); Assert.Equal( new[] { typeof(IEnumerable), typeof(IEnumerable) }, GetParameters(constructorCallSite)); @@ -105,12 +106,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup var callSite = callSiteFactory(type); // Assert - var transientCall = Assert.IsType(callSite); - Assert.IsType(transientCall.ServiceCallSite); + Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location); + var ctorCallSite = Assert.IsType(callSite); + Assert.Empty(ctorCallSite.ParameterCallSites); } public static TheoryData CreateCallSite_PicksConstructorWithTheMostNumberOfResolvedParametersData => - new TheoryData, Type[]> + new TheoryData, Type[]> { { typeof(TypeWithSupersetConstructors), @@ -192,17 +194,17 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup [Theory] [MemberData(nameof(CreateCallSite_PicksConstructorWithTheMostNumberOfResolvedParametersData))] - public void CreateCallSite_PicksConstructorWithTheMostNumberOfResolvedParameters( + private void CreateCallSite_PicksConstructorWithTheMostNumberOfResolvedParameters( Type type, - Func callSiteFactory, + Func callSiteFactory, Type[] expectedConstructorParameters) { // Act var callSite = callSiteFactory(type); // Assert - var transientCall = Assert.IsType(callSite); - var constructorCallSite = Assert.IsType(transientCall.ServiceCallSite); + Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location); + var constructorCallSite = Assert.IsType(callSite); Assert.Equal(expectedConstructorParameters, GetParameters(constructorCallSite)); } @@ -235,8 +237,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup [Theory] [MemberData(nameof(CreateCallSite_ConsidersConstructorsWithDefaultValuesData))] - public void CreateCallSite_ConsidersConstructorsWithDefaultValues( - Func callSiteFactory, + private void CreateCallSite_ConsidersConstructorsWithDefaultValues( + Func callSiteFactory, Type[] expectedConstructorParameters) { // Arrange @@ -246,8 +248,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup var callSite = callSiteFactory(type); // Assert - var transientCall = Assert.IsType(callSite); - var constructorCallSite = Assert.IsType(transientCall.ServiceCallSite); + Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location); + var constructorCallSite = Assert.IsType(callSite); Assert.Equal(expectedConstructorParameters, GetParameters(constructorCallSite)); } @@ -398,7 +400,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup Assert.StartsWith(expectedMessage, ex.Message); } - private static Func GetCallSiteFactory(params ServiceDescriptor[] descriptors) + private static Func GetCallSiteFactory(params ServiceDescriptor[] descriptors) { var collection = new ServiceCollection(); foreach (var descriptor in descriptors) @@ -408,7 +410,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup var callSiteFactory = new CallSiteFactory(collection.ToArray()); - return type => callSiteFactory.CreateCallSite(type, new CallSiteChain()); + return type => callSiteFactory.GetCallSite(type, new CallSiteChain()); } private static IEnumerable GetParameters(ConstructorCallSite constructorCallSite) => diff --git a/test/DI.Tests/ServiceProviderCompilationTest.cs b/test/DI.Tests/ServiceProviderCompilationTest.cs index 17a63b5..9774f8f 100644 --- a/test/DI.Tests/ServiceProviderCompilationTest.cs +++ b/test/DI.Tests/ServiceProviderCompilationTest.cs @@ -11,17 +11,10 @@ namespace Microsoft.Extensions.DependencyInjection.Tests public class ServiceProviderCompilationTest { [Theory] -#if DEBUG - [InlineData(ServiceProviderMode.Dynamic, typeof(I150))] - [InlineData(ServiceProviderMode.Runtime, typeof(I150))] - [InlineData(ServiceProviderMode.ILEmit, typeof(I150))] - [InlineData(ServiceProviderMode.Expressions, typeof(I150))] -#else - [InlineData(ServiceProviderMode.Dynamic, typeof(I200))] - [InlineData(ServiceProviderMode.Runtime, typeof(I200))] - [InlineData(ServiceProviderMode.ILEmit, typeof(I200))] - [InlineData(ServiceProviderMode.Expressions, typeof(I200))] -#endif + [InlineData(ServiceProviderMode.Dynamic, typeof(I999))] + [InlineData(ServiceProviderMode.Runtime, typeof(I999))] + [InlineData(ServiceProviderMode.ILEmit, typeof(I999))] + [InlineData(ServiceProviderMode.Expressions, typeof(I999))] private async Task CompilesInLimitedStackSpace(ServiceProviderMode mode, Type serviceType) { // Arrange