protected override object VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
        {
            // Check if we are in the situation where scoped service was promoted to singleton
            // and we need to lock the root
            RuntimeResolverLock 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)
            {
                Monitor.Enter(resolvedServices, ref lockTaken);
            }

            try
            {
                if (!resolvedServices.TryGetValue(scopedCallSite.Cache.Key, out var 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);
                }
            }
        }
        private object VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
        {
            bool lockTaken = false;
            Dictionary <ServiceCacheKey, object> 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)
            {
                Monitor.Enter(resolvedServices, ref lockTaken);
            }

            try
            {
                return(ResolveService(callSite, context, lockType, serviceProviderEngine));
            }
            finally
            {
                if (lockTaken)
                {
                    Monitor.Exit(resolvedServices);
                }
            }
        }
        private object ResolveService(ServiceCallSite callSite, RuntimeResolverContext context, RuntimeResolverLock lockType, ServiceProviderEngineScope serviceProviderEngine)
        {
            Dictionary <ServiceCacheKey, object> resolvedServices = serviceProviderEngine.ResolvedServices;

            if (!resolvedServices.TryGetValue(callSite.Cache.Key, out object resolved))
            {
                resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
                {
                    Scope         = serviceProviderEngine,
                    AcquiredLocks = context.AcquiredLocks | lockType
                });

                serviceProviderEngine.CaptureDisposable(resolved);
                resolvedServices.Add(callSite.Cache.Key, resolved);
            }

            return(resolved);
        }
        private object VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
        {
            bool   lockTaken = false;
            object sync      = serviceProviderEngine.Sync;
            Dictionary <ServiceCacheKey, object> 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)
            {
                Monitor.Enter(sync, ref lockTaken);
            }

            try
            {
                // Note: This method has already taken lock by the caller for resolution and access synchronization.
                // For scoped: takes a dictionary as both a resolution lock and a dictionary access lock.
                if (resolvedServices.TryGetValue(callSite.Cache.Key, out object resolved))
                {
                    return(resolved);
                }

                resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
                {
                    Scope         = serviceProviderEngine,
                    AcquiredLocks = context.AcquiredLocks | lockType
                });
                serviceProviderEngine.CaptureDisposable(resolved);
                resolvedServices.Add(callSite.Cache.Key, resolved);
                return(resolved);
            }
            finally
            {
                if (lockTaken)
                {
                    Monitor.Exit(sync);
                }
            }
        }
        private object ResolveService(ServiceCallSite callSite, RuntimeResolverContext context, RuntimeResolverLock lockType, ServiceProviderEngineScope serviceProviderEngine)
        {
            IDictionary <ServiceCacheKey, object> resolvedServices = serviceProviderEngine.ResolvedServices;

            // Note: This method has already taken lock by the caller for resolution and access synchronization.
            // For root: uses a concurrent dictionary and takes a per singleton lock for resolution.
            // For scoped: takes a dictionary as both a resolution lock and a dictionary access lock.
            Debug.Assert(
                (lockType == RuntimeResolverLock.Root && resolvedServices is ConcurrentDictionary <ServiceCacheKey, object>) ||
                (lockType == RuntimeResolverLock.Scope && Monitor.IsEntered(resolvedServices)));

            if (resolvedServices.TryGetValue(callSite.Cache.Key, out object resolved))
            {
                return(resolved);
            }

            resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
            {
                Scope         = serviceProviderEngine,
                AcquiredLocks = context.AcquiredLocks | lockType
            });
            serviceProviderEngine.CaptureDisposable(resolved);
            resolvedServices.Add(callSite.Cache.Key, resolved);
            return(resolved);
        }