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; } 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++)

View File

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

View File

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

View File

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

View File

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