Skip to content
This repository was archived by the owner on Nov 2, 2018. It is now read-only.

[Design] Reduce DI Allocations #289

Closed
wants to merge 11 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,32 @@ namespace Microsoft.Framework.DependencyInjection.ServiceLookup
internal class ServiceScope : IServiceScope
{
private readonly ServiceProvider _scopedProvider;

public ServiceScope(ServiceProvider scopedProvider)
private readonly ServiceScopeFactory _parentFactory;

public ServiceScope(ServiceProvider scopedProvider, ServiceScopeFactory parentFactory)
{
_scopedProvider = scopedProvider;
_parentFactory = parentFactory;
}

public IServiceProvider ServiceProvider
{
get { return _scopedProvider; }
}

internal void Reset()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why internal here? It would also be helpful to capture your explanation of this in comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what to write... Its a method that is explicitly bonded to the pooling of ServiceScopeFactory so if the class were made public rather than internal for whatever reason the exposed api would be for it to function as a standalone class (which is why I left public constructor in); whereas the "internals" only work as a class pair.

Not hugely fussed on visibility though, can make public

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave it internal

{
_scopedProvider.Reset();
}

public void Dispose()
{
_scopedProvider.Dispose();

if (_parentFactory != null)
{
_parentFactory.PoolScope(this);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
// 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.Collections.Concurrent;

namespace Microsoft.Framework.DependencyInjection.ServiceLookup
{
internal class ServiceScopeFactory : IServiceScopeFactory
{
private static int _capacity = 32 * Environment.ProcessorCount;
private static readonly ConcurrentQueue<ServiceScope> _scopePool = new ConcurrentQueue<ServiceScope>();

private readonly ServiceProvider _provider;

public ServiceScopeFactory(ServiceProvider provider)
Expand All @@ -14,7 +20,23 @@ public ServiceScopeFactory(ServiceProvider provider)

public IServiceScope CreateScope()
{
return new ServiceScope(new ServiceProvider(_provider));
ServiceScope scope;
if (_scopePool.TryDequeue(out scope))
{
scope.Reset();
return scope;
}
return new ServiceScope(new ServiceProvider(_provider), this);
}

internal void PoolScope(ServiceScope scope)
{
// Benign race condition
if (_scopePool.Count < _capacity)
{
_scopePool.Enqueue(scope);
}
}

}
}
17 changes: 15 additions & 2 deletions src/Microsoft.Framework.DependencyInjection/ServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal class ServiceProvider : IServiceProvider, IDisposable

private readonly Dictionary<IService, object> _resolvedServices = new Dictionary<IService, object>();
private ConcurrentBag<IDisposable> _disposables = new ConcurrentBag<IDisposable>();
private ConcurrentBag<IDisposable> _offlineDisposables;

public ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors)
{
Expand Down Expand Up @@ -143,16 +144,28 @@ internal IServiceCallSite GetResolveCallSite(IService service, ISet<Type> callSi
}
}

internal void Reset()
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it was done to avoid a double-dispose if Dispose() is called concurrently. With the current code it won't double-dispose any services but could call Clear() twice - is this a concern?

I suspect we're not too concerned about this scenario in practice. If there's any code you have operating on an SP while it's being disposed, you already have a bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would just empty any extras; or do nothing if there were none. I have a little concern as its a concurrentbag which suggests items can be added in parallel; or out of order and with delays.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact next comment is about the forced double dispose.

if (_offlineDisposables == null)
{
throw new InvalidOperationException("ServiceProvider reused before being disposed");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a resx in this project? Programmatic names like ServiceProvider need to be formatted in with parameters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done?

}
_disposables = _offlineDisposables;
_offlineDisposables = null;
}

public void Dispose()
{
var disposables = Interlocked.Exchange(ref _disposables, null);

if (disposables != null)
{
foreach (var disposable in disposables)
IDisposable disposable;
while (disposables.TryTake(out disposable))
{
disposable.Dispose();
}
_resolvedServices.Clear();
_offlineDisposables = disposables;
}
}

Expand Down