Skip to content

Commit c36a0e0

Browse files
authored
Don't cache Endpoints if a source throws (#43729)
1 parent c00c22e commit c36a0e0

File tree

2 files changed

+45
-5
lines changed

2 files changed

+45
-5
lines changed

src/Http/Routing/src/CompositeEndpointDataSource.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,7 @@ private void HandleChange(bool collectionChanged)
242242
[MemberNotNull(nameof(_consumerChangeToken))]
243243
private void CreateChangeTokenUnsynchronized(bool collectionChanged)
244244
{
245-
_cts = new CancellationTokenSource();
246-
_consumerChangeToken = new CancellationChangeToken(_cts.Token);
245+
var cts = new CancellationTokenSource();
247246

248247
if (collectionChanged)
249248
{
@@ -255,17 +254,24 @@ private void CreateChangeTokenUnsynchronized(bool collectionChanged)
255254
() => HandleChange(collectionChanged: false)));
256255
}
257256
}
257+
258+
_cts = cts;
259+
_consumerChangeToken = new CancellationChangeToken(cts.Token);
258260
}
259261

260262
[MemberNotNull(nameof(_endpoints))]
261263
private void CreateEndpointsUnsynchronized()
262264
{
263-
_endpoints = new List<Endpoint>();
265+
var endpoints = new List<Endpoint>();
264266

265267
foreach (var dataSource in _dataSources)
266268
{
267-
_endpoints.AddRange(dataSource.Endpoints);
269+
endpoints.AddRange(dataSource.Endpoints);
268270
}
271+
272+
// Only cache _endpoints after everything succeeds without throwing.
273+
// We don't want to create a negative cache which would cause 404s when there should be 500s.
274+
_endpoints = endpoints;
269275
}
270276

271277
// Use private variable '_endpoints' to avoid initialization

src/Http/Routing/test/UnitTests/CompositeEndpointDataSourceTest.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.ObjectModel;
5-
using System.Linq;
65
using Microsoft.AspNetCore.Builder;
76
using Microsoft.AspNetCore.Http;
87
using Microsoft.AspNetCore.Routing.Patterns;
@@ -58,6 +57,28 @@ public void CreatesShallowCopyOf_ListOfGroupedEndpoints()
5857
Assert.Equal(groupedEndpoints, resolvedGroupEndpoints);
5958
}
6059

60+
[Fact]
61+
public void RepeatedlyThrows_WhenChildDataSourcesThrow()
62+
{
63+
var ex = new Exception();
64+
var compositeDataSource = new CompositeEndpointDataSource(new[]
65+
{
66+
new EndpointThrowingDataSource(ex),
67+
});
68+
var groupContext = new RouteGroupContext
69+
{
70+
Prefix = RoutePatternFactory.Parse(""),
71+
Conventions = Array.Empty<Action<EndpointBuilder>>(),
72+
FinallyConventions = Array.Empty<Action<EndpointBuilder>>(),
73+
ApplicationServices = new ServiceCollection().BuildServiceProvider(),
74+
};
75+
76+
Assert.Same(ex, Assert.Throws<Exception>(() => compositeDataSource.Endpoints));
77+
Assert.Same(ex, Assert.Throws<Exception>(() => compositeDataSource.Endpoints));
78+
Assert.Same(ex, Assert.Throws<Exception>(() => compositeDataSource.GetGroupedEndpoints(groupContext)));
79+
Assert.Same(ex, Assert.Throws<Exception>(() => compositeDataSource.GetGroupedEndpoints(groupContext)));
80+
}
81+
6182
[Fact]
6283
public void Endpoints_ReturnsAllEndpoints_FromMultipleDataSources()
6384
{
@@ -502,4 +523,17 @@ public override IReadOnlyList<Endpoint> GetGroupedEndpoints(RouteGroupContext co
502523

503524
public override IChangeToken GetChangeToken() => NullChangeToken.Singleton;
504525
}
526+
527+
private class EndpointThrowingDataSource : EndpointDataSource
528+
{
529+
private readonly Exception _ex;
530+
531+
public EndpointThrowingDataSource(Exception ex)
532+
{
533+
_ex = ex;
534+
}
535+
536+
public override IReadOnlyList<Endpoint> Endpoints => throw _ex;
537+
public override IChangeToken GetChangeToken() => NullChangeToken.Singleton;
538+
}
505539
}

0 commit comments

Comments
 (0)