Recreate cache of endpoints when data source updated (#503)

Addresses #454
This commit is contained in:
Jass Bagga 2018-01-03 14:33:16 -08:00 committed by GitHub
parent a50780f8e3
commit c66d5240d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 122 additions and 10 deletions

View File

@ -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++)

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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();
}
}
}
}