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

Commit 000fff9

Browse files
committed
Dispose services in reverse creation order
1 parent e9def8c commit 000fff9

File tree

5 files changed

+95
-16
lines changed

5 files changed

+95
-16
lines changed

src/Microsoft.Extensions.DependencyInjection.Specification.Tests/DependencyInjectionSpecificationTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,5 +655,30 @@ public void ServiceContainerPicksConstructorWithLongestMatches(
655655
Assert.Same(expected.MultipleService, actual.MultipleService);
656656
Assert.Same(expected.ScopedService, actual.ScopedService);
657657
}
658+
659+
[Fact]
660+
public void DisposesInReverseOrderOfCreation()
661+
{
662+
// Arrange
663+
var serviceCollection = new TestServiceCollection();
664+
serviceCollection.AddSingleton<FakeDisposeCallback>();
665+
serviceCollection.AddSingleton<IFakeOuterService, FakeDisposableCallbackOuterService>();
666+
serviceCollection.AddSingleton<IFakeMultipleService, FakeDisposableCallbackInnerService>();
667+
serviceCollection.AddSingleton<IFakeMultipleService, FakeDisposableCallbackInnerService>();
668+
serviceCollection.AddSingleton<IFakeMultipleService, FakeDisposableCallbackInnerService>();
669+
serviceCollection.AddSingleton<IFakeService, FakeDisposableCallbackInnerService>();
670+
var serviceProvider = CreateServiceProvider(serviceCollection);
671+
672+
var callback = serviceProvider.GetService<FakeDisposeCallback>();
673+
var outer = serviceProvider.GetService<IFakeOuterService>();
674+
675+
// Act
676+
((IDisposable)serviceProvider).Dispose();
677+
678+
// Assert
679+
Assert.Equal(outer, callback.Disposed[0]);
680+
Assert.Equal(outer.MultipleServices.Reverse(), callback.Disposed.Skip(1).Take(3).OfType<IFakeMultipleService>());
681+
Assert.Equal(outer.SingleService, callback.Disposed[4]);
682+
}
658683
}
659684
}

src/Microsoft.Extensions.DependencyInjection.Specification.Tests/Fakes/FakeService.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,61 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
56

67
namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
78
{
9+
public class FakeDisposeCallback
10+
{
11+
public List<object> Disposed { get; } = new List<object>();
12+
}
13+
14+
public class FakeDisposableCallbackService: IDisposable
15+
{
16+
private static int _globalId;
17+
private readonly int _id;
18+
private readonly FakeDisposeCallback _callback;
19+
20+
public FakeDisposableCallbackService(FakeDisposeCallback callback)
21+
{
22+
_id = _globalId++;
23+
_callback = callback;
24+
}
25+
26+
public void Dispose()
27+
{
28+
_callback.Disposed.Add(this);
29+
}
30+
31+
public override string ToString()
32+
{
33+
return _id.ToString();
34+
}
35+
}
36+
37+
public class FakeDisposableCallbackOuterService : FakeDisposableCallbackService, IFakeOuterService
38+
{
39+
public FakeDisposableCallbackOuterService(
40+
IFakeService singleService,
41+
IEnumerable<IFakeMultipleService> multipleServices,
42+
FakeDisposeCallback callback) : base(callback)
43+
{
44+
SingleService = singleService;
45+
MultipleServices = multipleServices;
46+
}
47+
48+
public IFakeService SingleService { get; }
49+
public IEnumerable<IFakeMultipleService> MultipleServices { get; }
50+
}
51+
52+
53+
public class FakeDisposableCallbackInnerService : FakeDisposableCallbackService, IFakeMultipleService
54+
{
55+
public FakeDisposableCallbackInnerService(FakeDisposeCallback callback) : base(callback)
56+
{
57+
}
58+
}
59+
860
public class FakeService : IFakeEveryService, IDisposable
961
{
1062
public PocoClass Value { get; set; }

src/Microsoft.Extensions.DependencyInjection/ServiceLookup/CallSiteExpressionBuilder.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -184,34 +184,42 @@ protected override Expression VisitScoped(ScopedCallSite callSite, ParameterExpr
184184
callSite.Key,
185185
typeof(object));
186186

187-
var resolvedExpression = Expression.Variable(typeof(object), "resolved");
187+
var resolvedVariable = Expression.Variable(typeof(object), "resolved");
188188

189189
var resolvedServices = GetResolvedServices(provider);
190190

191191
var tryGetValueExpression = Expression.Call(
192192
resolvedServices,
193193
TryGetValueMethodInfo,
194194
keyExpression,
195-
resolvedExpression);
195+
resolvedVariable);
196196

197197
var assignExpression = Expression.Assign(
198-
resolvedExpression, VisitCallSite(callSite.ServiceCallSite, provider));
198+
resolvedVariable, VisitCallSite(callSite.ServiceCallSite, provider));
199199

200200
var addValueExpression = Expression.Call(
201201
resolvedServices,
202202
AddMethodInfo,
203203
keyExpression,
204-
resolvedExpression);
204+
resolvedVariable);
205+
206+
Expression captureDisposableExpression = Expression.Invoke(
207+
GetCaptureDisposable(provider),
208+
resolvedVariable
209+
);
205210

206211
var blockExpression = Expression.Block(
207212
typeof(object),
208213
new[] {
209-
resolvedExpression
214+
resolvedVariable
210215
},
211216
Expression.IfThen(
212217
Expression.Not(tryGetValueExpression),
213-
Expression.Block(assignExpression, addValueExpression)),
214-
resolvedExpression);
218+
Expression.Block(
219+
assignExpression,
220+
addValueExpression,
221+
captureDisposableExpression)),
222+
resolvedVariable);
215223

216224
return blockExpression;
217225
}

src/Microsoft.Extensions.DependencyInjection/ServiceLookup/CallSiteRuntimeResolver.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ protected override object VisitScoped(ScopedCallSite scopedCallSite, ServiceProv
4949
if (!provider.ResolvedServices.TryGetValue(scopedCallSite.Key, out resolved))
5050
{
5151
resolved = VisitCallSite(scopedCallSite.ServiceCallSite, provider);
52+
provider.CaptureDisposable(resolved);
5253
provider.ResolvedServices.Add(scopedCallSite.Key, resolved);
5354
}
5455
}

src/Microsoft.Extensions.DependencyInjection/ServiceProvider.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -169,22 +169,15 @@ public void Dispose()
169169
_disposeCalled = true;
170170
if (_transientDisposables != null)
171171
{
172-
foreach (var disposable in _transientDisposables)
172+
for (int i = _transientDisposables.Count - 1; i >= 0; i--)
173173
{
174+
var disposable = _transientDisposables[i];
174175
disposable.Dispose();
175176
}
176177

177178
_transientDisposables.Clear();
178179
}
179180

180-
// PERF: We've enumerating the dictionary so that we don't allocate to enumerate.
181-
// .Values allocates a ValueCollection on the heap, enumerating the dictionary allocates
182-
// a struct enumerator
183-
foreach (var entry in ResolvedServices)
184-
{
185-
(entry.Value as IDisposable)?.Dispose();
186-
}
187-
188181
ResolvedServices.Clear();
189182
}
190183
}

0 commit comments

Comments
 (0)