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; }
|
public override IChangeToken ChangeToken { get; }
|
||||||
|
|
||||||
protected override IReadOnlyList<Address> GetAddesses()
|
protected override IReadOnlyList<Address> GetAddresses()
|
||||||
{
|
{
|
||||||
var addresses = new List<Address>();
|
var addresses = new List<Address>();
|
||||||
for (var i = 0; i < _dataSources.Length; i++)
|
for (var i = 0; i < _dataSources.Length; i++)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
public IList<Endpoint> Endpoints => _endpoints;
|
public IList<Endpoint> Endpoints => _endpoints;
|
||||||
|
|
||||||
protected override IReadOnlyList<Address> GetAddesses() => _addresses;
|
protected override IReadOnlyList<Address> GetAddresses() => _addresses;
|
||||||
|
|
||||||
protected override IReadOnlyList<Endpoint> GetEndpoints() => _endpoints;
|
protected override IReadOnlyList<Endpoint> GetEndpoints() => _endpoints;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
public abstract IChangeToken ChangeToken { get; }
|
public abstract IChangeToken ChangeToken { get; }
|
||||||
|
|
||||||
protected abstract IReadOnlyList<Address> GetAddesses();
|
protected abstract IReadOnlyList<Address> GetAddresses();
|
||||||
|
|
||||||
protected abstract IReadOnlyList<Endpoint> GetEndpoints();
|
protected abstract IReadOnlyList<Endpoint> GetEndpoints();
|
||||||
|
|
||||||
IReadOnlyList<Address> IAddressCollectionProvider.Addresses => GetAddesses();
|
IReadOnlyList<Address> IAddressCollectionProvider.Addresses => GetAddresses();
|
||||||
|
|
||||||
IReadOnlyList<Endpoint> IEndpointCollectionProvider.Endpoints => GetEndpoints();
|
IReadOnlyList<Endpoint> IEndpointCollectionProvider.Endpoints => GetEndpoints();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -12,11 +11,13 @@ using Microsoft.AspNetCore.Dispatcher.Patterns;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
|
using Primitives = Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
public class TreeMatcher : MatcherBase
|
public class TreeMatcher : MatcherBase
|
||||||
{
|
{
|
||||||
|
private bool _onChange;
|
||||||
private bool _dataInitialized;
|
private bool _dataInitialized;
|
||||||
private object _lock;
|
private object _lock;
|
||||||
private Cache _cache;
|
private Cache _cache;
|
||||||
|
|
@ -30,8 +31,6 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
_initializer = CreateCache;
|
_initializer = CreateCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Version { get; private set; }
|
|
||||||
|
|
||||||
public override async Task MatchAsync(MatcherContext context)
|
public override async Task MatchAsync(MatcherContext context)
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
|
|
@ -39,7 +38,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureServicesInitialized(context);
|
EnsureTreeMatcherServicesInitialized(context);
|
||||||
|
|
||||||
var cache = LazyInitializer.EnsureInitialized(ref _cache, ref _dataInitialized, ref _lock, _initializer);
|
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)
|
private bool MatchConstraints(HttpContext httpContext, DispatcherValueCollection values, IDictionary<string, IDispatcherValueConstraint> constraints)
|
||||||
{
|
{
|
||||||
if (constraints != null)
|
if (constraints != null)
|
||||||
|
|
@ -136,8 +153,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
var endpoint = endpoints[i];
|
var endpoint = endpoints[i];
|
||||||
|
|
||||||
var templateEndpoint = endpoint as IRoutePatternEndpoint;
|
if (!(endpoint is IRoutePatternEndpoint templateEndpoint))
|
||||||
if (templateEndpoint == null)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -776,6 +780,37 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Null(context.Endpoint);
|
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)
|
private static MatcherContext CreateMatcherContext(string requestPath)
|
||||||
{
|
{
|
||||||
var request = new Mock<HttpRequest>(MockBehavior.Strict);
|
var request = new Mock<HttpRequest>(MockBehavior.Strict);
|
||||||
|
|
@ -804,5 +839,66 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
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