File: System\Security\Cryptography\X509Certificates\OpenSslCachedDirectoryStoreProvider.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.IO;
using Microsoft.Win32.SafeHandles;
 
namespace System.Security.Cryptography.X509Certificates
{
    internal sealed class OpenSslCachedDirectoryStoreProvider
    {
        // These intervals are mostly arbitrary.
        // Prior to this caching these stores were always read "hot" from disk, and 30 seconds
        // seems like "long enough" for performance gains with "short enough" that if the filesystem
        // has LastWrite updating disabled that the process will be mostly responsive.
        private static readonly TimeSpan s_lastWriteRecheckInterval = TimeSpan.FromSeconds(1);
        private static readonly TimeSpan s_assumeInvalidInterval = TimeSpan.FromSeconds(30);
 
        private readonly Stopwatch _recheckStopwatch = new Stopwatch();
        private readonly DirectoryInfo _storeDirectoryInfo;
 
        private SafeX509StackHandle? _nativeCollection;
        private DateTime _loadLastWrite;
        private bool _forceRefresh;
 
        internal OpenSslCachedDirectoryStoreProvider(string storeName)
        {
            string storePath = OpenSslDirectoryBasedStoreProvider.GetStorePath(storeName);
            _storeDirectoryInfo = new DirectoryInfo(storePath);
        }
 
        internal void DoRefresh()
        {
            _forceRefresh = true;
        }
 
        internal SafeX509StackHandle GetNativeCollection()
        {
            SafeX509StackHandle? ret = _nativeCollection;
 
            TimeSpan elapsed = _recheckStopwatch.Elapsed;
 
            if (ret == null || elapsed >= s_lastWriteRecheckInterval || _forceRefresh)
            {
                lock (_recheckStopwatch)
                {
                    _storeDirectoryInfo.Refresh();
                    DirectoryInfo info = _storeDirectoryInfo;
                    ret = _nativeCollection;
                    elapsed = _recheckStopwatch.Elapsed;
 
                    if (ret == null ||
                        _forceRefresh ||
                        elapsed >= s_assumeInvalidInterval ||
                        (info.Exists && info.LastWriteTimeUtc != _loadLastWrite))
                    {
                        SafeX509StackHandle newColl = Interop.Crypto.NewX509Stack();
                        Interop.Crypto.CheckValidOpenSslHandle(newColl);
 
                        if (info.Exists)
                        {
                            Interop.Crypto.X509StackAddDirectoryStore(newColl, info.FullName);
                            _loadLastWrite = info.LastWriteTimeUtc;
                        }
 
 
                        // The existing collection is not Disposed here, intentionally.
                        // It could be in the gap between when they are returned from this method and
                        // not yet used in a P/Invoke, which would result in an exception being thrown.
                        // In order to maintain "finalization-free" this method would need to always
                        // DangerousAddRef, and the callers would need to DangerousRelease,
                        // adding more interlocked operations on every call.
                        ret = newColl;
                        _nativeCollection = newColl;
                        _recheckStopwatch.Restart();
                        _forceRefresh = false;
                    }
                }
            }
 
            Debug.Assert(ret != null);
            return ret;
        }
    }
}