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

Commit 170848f

Browse files
committed
Dispose services in reverse creation order
1 parent d783e79 commit 170848f

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
@@ -164,34 +164,42 @@ protected override Expression VisitScoped(ScopedCallSite callSite, ParameterExpr
164164
callSite.Key,
165165
typeof(object));
166166

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

169169
var resolvedServices = GetResolvedServices(provider);
170170

171171
var tryGetValueExpression = Expression.Call(
172172
resolvedServices,
173173
TryGetValueMethodInfo,
174174
keyExpression,
175-
resolvedExpression);
175+
resolvedVariable);
176176

177177
var assignExpression = Expression.Assign(
178-
resolvedExpression, VisitCallSite(callSite.ServiceCallSite, provider));
178+
resolvedVariable, VisitCallSite(callSite.ServiceCallSite, provider));
179179

180180
var addValueExpression = Expression.Call(
181181
resolvedServices,
182182
AddMethodInfo,
183183
keyExpression,
184-
resolvedExpression);
184+
resolvedVariable);
185+
186+
Expression captureDisposableExpression = Expression.Invoke(
187+
GetCaptureDisposable(provider),
188+
resolvedVariable
189+
);
185190

186191
var blockExpression = Expression.Block(
187192
typeof(object),
188193
new[] {
189-
resolvedExpression
194+
resolvedVariable
190195
},
191196
Expression.IfThen(
192197
Expression.Not(tryGetValueExpression),
193-
Expression.Block(assignExpression, addValueExpression)),
194-
resolvedExpression);
198+
Expression.Block(
199+
assignExpression,
200+
addValueExpression,
201+
captureDisposableExpression)),
202+
resolvedVariable);
195203

196204
return blockExpression;
197205
}

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
@@ -165,22 +165,15 @@ public void Dispose()
165165
_disposeCalled = true;
166166
if (_transientDisposables != null)
167167
{
168-
foreach (var disposable in _transientDisposables)
168+
for (int i = _transientDisposables.Count - 1; i >= 0; i--)
169169
{
170+
var disposable = _transientDisposables[i];
170171
disposable.Dispose();
171172
}
172173

173174
_transientDisposables.Clear();
174175
}
175176

176-
// PERF: We've enumerating the dictionary so that we don't allocate to enumerate.
177-
// .Values allocates a ValueCollection on the heap, enumerating the dictionary allocates
178-
// a struct enumerator
179-
foreach (var entry in ResolvedServices)
180-
{
181-
(entry.Value as IDisposable)?.Dispose();
182-
}
183-
184177
ResolvedServices.Clear();
185178
}
186179
}

0 commit comments

Comments
 (0)