| File: DataSourceDependentCache.cs | Web Access |
| Project: src\aspnetcore\src\Http\Routing\src\Microsoft.AspNetCore.Routing.csproj (Microsoft.AspNetCore.Routing) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable enable using System.Diagnostics.CodeAnalysis; 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) { ArgumentNullException.ThrowIfNull(dataSource); ArgumentNullException.ThrowIfNull(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. [NotNullIfNotNull(nameof(_value))] public T? Value => _value; [MemberNotNull(nameof(_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; } } } }