aspnetcore/src/Http/Routing/src/DataSourceDependentCache.cs

93 lines
3.0 KiB
C#

// 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.Threading;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing
{
// FYI: This class is also linked into MVC. If you make changes to the API you will
// also need to change MVC's usage.
internal sealed class DataSourceDependentCache<T> : IDisposable where T : class
{
private readonly EndpointDataSource _dataSource;
private readonly Func<IReadOnlyList<Endpoint>, T> _initializeCore;
private readonly Func<T> _initializer;
private readonly Action<object> _initializerWithState;
private object _lock;
private bool _initialized;
private T _value;
private IDisposable _disposable;
private bool _disposed;
public DataSourceDependentCache(EndpointDataSource dataSource, Func<IReadOnlyList<Endpoint>, T> initialize)
{
if (dataSource == null)
{
throw new ArgumentNullException(nameof(dataSource));
}
if (initialize == null)
{
throw new ArgumentNullException(nameof(initialize));
}
_dataSource = dataSource;
_initializeCore = initialize;
_initializer = Initialize;
_initializerWithState = (state) => Initialize();
_lock = new object();
}
// Note that we don't lock here, and think about that in the context of a 'push'. So when data gets 'pushed'
// we start computing a new state, but we're still able to perform operations on the old state until we've
// processed the update.
public T Value => _value;
public T EnsureInitialized()
{
return LazyInitializer.EnsureInitialized<T>(ref _value, ref _initialized, ref _lock, _initializer);
}
private T Initialize()
{
lock (_lock)
{
var changeToken = _dataSource.GetChangeToken();
_value = _initializeCore(_dataSource.Endpoints);
// Don't resubscribe if we're already disposed.
if (_disposed)
{
return _value;
}
_disposable = changeToken.RegisterChangeCallback(_initializerWithState, null);
return _value;
}
}
public void Dispose()
{
lock (_lock)
{
if (!_disposed)
{
_disposable?.Dispose();
_disposable = null;
// Tracking whether we're disposed or not prevents a race-condition
// between disposal and Initialize(). If a change callback fires after
// we dispose, then we don't want to reregister.
_disposed = true;
}
}
}
}
}