Recreate cache of endpoints when data source updated (#503)
Addresses #454
This commit is contained in:
parent
a50780f8e3
commit
c66d5240d1
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
|
||||
public override IChangeToken ChangeToken { get; }
|
||||
|
||||
protected override IReadOnlyList<Address> GetAddesses()
|
||||
protected override IReadOnlyList<Address> GetAddresses()
|
||||
{
|
||||
var addresses = new List<Address>();
|
||||
for (var i = 0; i < _dataSources.Length; i++)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
|
||||
public IList<Endpoint> Endpoints => _endpoints;
|
||||
|
||||
protected override IReadOnlyList<Address> GetAddesses() => _addresses;
|
||||
protected override IReadOnlyList<Address> GetAddresses() => _addresses;
|
||||
|
||||
protected override IReadOnlyList<Endpoint> GetEndpoints() => _endpoints;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
public abstract IChangeToken ChangeToken { get; }
|
||||
|
||||
protected abstract IReadOnlyList<Address> GetAddesses();
|
||||
protected abstract IReadOnlyList<Address> GetAddresses();
|
||||
|
||||
protected abstract IReadOnlyList<Endpoint> GetEndpoints();
|
||||
|
||||
IReadOnlyList<Address> IAddressCollectionProvider.Addresses => GetAddesses();
|
||||
IReadOnlyList<Address> IAddressCollectionProvider.Addresses => GetAddresses();
|
||||
|
||||
IReadOnlyList<Endpoint> IEndpointCollectionProvider.Endpoints => GetEndpoints();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -12,11 +11,13 @@ using Microsoft.AspNetCore.Dispatcher.Patterns;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Primitives = Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class TreeMatcher : MatcherBase
|
||||
{
|
||||
private bool _onChange;
|
||||
private bool _dataInitialized;
|
||||
private object _lock;
|
||||
private Cache _cache;
|
||||
|
|
@ -30,8 +31,6 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
_initializer = CreateCache;
|
||||
}
|
||||
|
||||
public int Version { get; private set; }
|
||||
|
||||
public override async Task MatchAsync(MatcherContext context)
|
||||
{
|
||||
if (context == null)
|
||||
|
|
@ -39,7 +38,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
EnsureServicesInitialized(context);
|
||||
EnsureTreeMatcherServicesInitialized(context);
|
||||
|
||||
var cache = LazyInitializer.EnsureInitialized(ref _cache, ref _dataInitialized, ref _lock, _initializer);
|
||||
|
||||
|
|
@ -101,6 +100,24 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
}
|
||||
}
|
||||
|
||||
private void EnsureTreeMatcherServicesInitialized(MatcherContext context)
|
||||
{
|
||||
EnsureServicesInitialized(context);
|
||||
if (Volatile.Read(ref _onChange))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (!Volatile.Read(ref _onChange))
|
||||
{
|
||||
_onChange = true;
|
||||
Primitives.ChangeToken.OnChange(() => ChangeToken, () => Volatile.Write(ref _dataInitialized, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool MatchConstraints(HttpContext httpContext, DispatcherValueCollection values, IDictionary<string, IDispatcherValueConstraint> constraints)
|
||||
{
|
||||
if (constraints != null)
|
||||
|
|
@ -136,8 +153,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
var endpoint = endpoints[i];
|
||||
|
||||
var templateEndpoint = endpoint as IRoutePatternEndpoint;
|
||||
if (templateEndpoint == null)
|
||||
if (!(endpoint is IRoutePatternEndpoint templateEndpoint))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +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.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -776,6 +780,37 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
Assert.Null(context.Endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChangeTokenTriggers_CreateCache()
|
||||
{
|
||||
// Arrange 1
|
||||
var factory = new TreeMatcherFactory();
|
||||
var dataSource = new TestDataSource(new List<Endpoint>() { new RoutePatternEndpoint("test/{p1}", new object(), Test_Delegate, "Test"), });
|
||||
var matcher = factory.CreateMatcher(dataSource, new List<EndpointSelector>());
|
||||
var context = CreateMatcherContext("/test/parameter1");
|
||||
|
||||
// Act 1
|
||||
await matcher.MatchAsync(context);
|
||||
|
||||
// Assert 1
|
||||
Assert.Equal("Test", context.Endpoint.DisplayName);
|
||||
context.Values.TryGetValue("p1", out var value);
|
||||
Assert.Equal("parameter1", value);
|
||||
|
||||
// Arrange 2
|
||||
dataSource.UpdateEndpoints(new List<Endpoint>() { new RoutePatternEndpoint("test2/{p2}", new object(), Test_Delegate, "Test2"), });
|
||||
context = CreateMatcherContext("/test2/parameter2");
|
||||
|
||||
// Act 2
|
||||
await matcher.MatchAsync(context);
|
||||
|
||||
// Assert 2
|
||||
Assert.Equal("Test2", context.Endpoint.DisplayName);
|
||||
Assert.False(context.Values.TryGetValue("p1", out value));
|
||||
context.Values.TryGetValue("p2", out value);
|
||||
Assert.Equal("parameter2", value);
|
||||
}
|
||||
|
||||
private static MatcherContext CreateMatcherContext(string requestPath)
|
||||
{
|
||||
var request = new Mock<HttpRequest>(MockBehavior.Strict);
|
||||
|
|
@ -804,5 +839,66 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public class TestDataSource : DispatcherDataSource, IAddressCollectionProvider, IEndpointCollectionProvider
|
||||
{
|
||||
private IList<Address> _addresses;
|
||||
private IList<Endpoint> _endpoints;
|
||||
private TestChangeToken _changeToken;
|
||||
private readonly object _lock = new object();
|
||||
|
||||
public TestDataSource(IList<Endpoint> endpoints)
|
||||
{
|
||||
_addresses = new List<Address>();
|
||||
_endpoints = endpoints;
|
||||
_changeToken = new TestChangeToken();
|
||||
}
|
||||
|
||||
public void UpdateEndpoints(IList<Endpoint> endpoints)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_endpoints = endpoints;
|
||||
// Trigger change token here
|
||||
var previousToken = _changeToken;
|
||||
_changeToken = new TestChangeToken();
|
||||
previousToken.OnChange();
|
||||
}
|
||||
}
|
||||
|
||||
public override IChangeToken ChangeToken
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _changeToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IReadOnlyList<Address> IAddressCollectionProvider.Addresses => GetAddresses();
|
||||
|
||||
IReadOnlyList<Endpoint> IEndpointCollectionProvider.Endpoints => GetEndpoints();
|
||||
|
||||
protected override IReadOnlyList<Address> GetAddresses() => _addresses.ToList();
|
||||
|
||||
protected override IReadOnlyList<Endpoint> GetEndpoints() => _endpoints.ToList();
|
||||
|
||||
public IList<Endpoint> Endpoints => _endpoints;
|
||||
|
||||
private class TestChangeToken : IChangeToken
|
||||
{
|
||||
private CancellationTokenSource _cts = new CancellationTokenSource();
|
||||
|
||||
public bool HasChanged => true;
|
||||
|
||||
public bool ActiveChangeCallbacks => true;
|
||||
|
||||
public IDisposable RegisterChangeCallback(Action<object> callback, object state) => _cts.Token.Register(callback, state);
|
||||
|
||||
public void OnChange() => _cts.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue