diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-09-02 07:06:08 +0200 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-09-02 07:06:08 +0200 |
| commit | d73e3d964e3ce8197ec86bdaad447072305bc0d6 (patch) | |
| tree | ac6e3add8f901f4b17fe5815fbfc30832235d955 /MediaBrowser.Controller | |
| parent | e5dc3b8a54fdad218ba062f8adb58fb5e42d8f9d (diff) | |
Optimize Caches
Co-Authored-By: Cody Robibero <cody@robibe.ro>
Diffstat (limited to 'MediaBrowser.Controller')
| -rw-r--r-- | MediaBrowser.Controller/Providers/DirectoryService.cs | 73 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Providers/IDirectoryService.cs | 6 |
2 files changed, 57 insertions, 22 deletions
diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index 43f0f11dfe..336e35e293 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using BitFaster.Caching.Lru; using MediaBrowser.Model.IO; @@ -11,33 +12,20 @@ namespace MediaBrowser.Controller.Providers { public class DirectoryService : IDirectoryService { - private const int DirectoryCacheSize = 2048; - private const int FileCacheSize = 4096; - - // A bounded LRU sizes its table up front, so it costs several kilobytes while still empty. - // One instance is a DI singleton and lives for the process, but the library code also news - // one up per item in several loops and hands it to QueueRefresh, which holds on to it until - // the refresh runs. Those instances usually ask about a single path, so each cache waits - // until something actually looks in it. - private readonly Lazy<FastConcurrentLru<string, FileSystemMetadata[]>> _cache - = new(static () => new(Environment.ProcessorCount, DirectoryCacheSize, StringComparer.Ordinal)); - - private readonly Lazy<FastConcurrentLru<string, FileSystemMetadata>> _fileCache - = new(static () => new(Environment.ProcessorCount, FileCacheSize, StringComparer.Ordinal)); - - private readonly Lazy<FastConcurrentLru<string, List<string>>> _filePathCache - = new(static () => new(Environment.ProcessorCount, DirectoryCacheSize, StringComparer.Ordinal)); + private static readonly ConditionalWeakTable<IFileSystem, DirectoryCache> _caches = []; private readonly IFileSystem _fileSystem; + private readonly DirectoryCache _cache; public DirectoryService(IFileSystem fileSystem) { _fileSystem = fileSystem; + _cache = _caches.GetValue(fileSystem, static _ => new DirectoryCache()); } public FileSystemMetadata[] GetFileSystemEntries(string path) { - return _cache.Value.GetOrAdd( + return _cache.Entries.GetOrAdd( path, static (p, fileSystem) => { @@ -99,7 +87,7 @@ namespace MediaBrowser.Controller.Providers public FileSystemMetadata? GetFileSystemEntry(string path) { - if (!_fileCache.Value.TryGet(path, out var result)) + if (!_cache.Files.TryGet(path, out var result)) { var file = _fileSystem.GetFileSystemInfo(path); @@ -108,7 +96,7 @@ namespace MediaBrowser.Controller.Providers if (file?.Exists ?? false) { result = file; - _fileCache.Value.AddOrUpdate(path, result); + _cache.Files.AddOrUpdate(path, result); } } @@ -122,10 +110,12 @@ namespace MediaBrowser.Controller.Providers { if (clearCache) { - _filePathCache.Value.TryRemove(path, out _); + // Only what is remembered about this directory. Invalidate() also drops the parent, + // which a write needs but which is needless churn on a shared cache here. + Forget(path); } - var filePaths = _filePathCache.Value.GetOrAdd( + return _cache.FilePaths.GetOrAdd( path, static (p, fileSystem) => { @@ -139,13 +129,52 @@ namespace MediaBrowser.Controller.Providers } }, _fileSystem); + } - return filePaths; + public void Invalidate(string path) + { + // Everything remembered about the path itself, and the listing of the directory holding + // it, since writing a file changes what its directory contains. The caches belong to the + // file system rather than to this instance, so this is felt by every reader of it. + Forget(path); + + var parent = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(parent)) + { + Forget(parent); + } } public bool IsAccessible(string path) { return _fileSystem.GetFileSystemEntryPaths(path).Any(); } + + private void Forget(string path) + { + _cache.Entries.TryRemove(path, out _); + _cache.Files.TryRemove(path, out _); + _cache.FilePaths.TryRemove(path, out _); + } + + private sealed class DirectoryCache + { + private const int DirectoryCacheSize = 2048; + private const int FileCacheSize = 8192; + + // A DirectoryService no longer bounds how long its answers are trusted by dying, so a + // lifetime does. This is a staleness bound, not a snapshot: a long refresh can outlive it + // and re-read a directory partway through. + private static readonly TimeSpan _entryLifetime = TimeSpan.FromMinutes(1); + + public ConcurrentTLru<string, FileSystemMetadata[]> Entries { get; } + = new(Environment.ProcessorCount, DirectoryCacheSize, StringComparer.Ordinal, _entryLifetime); + + public ConcurrentTLru<string, FileSystemMetadata> Files { get; } + = new(Environment.ProcessorCount, FileCacheSize, StringComparer.Ordinal, _entryLifetime); + + public ConcurrentTLru<string, List<string>> FilePaths { get; } + = new(Environment.ProcessorCount, DirectoryCacheSize, StringComparer.Ordinal, _entryLifetime); + } } } diff --git a/MediaBrowser.Controller/Providers/IDirectoryService.cs b/MediaBrowser.Controller/Providers/IDirectoryService.cs index 8a3fa33da3..609d094254 100644 --- a/MediaBrowser.Controller/Providers/IDirectoryService.cs +++ b/MediaBrowser.Controller/Providers/IDirectoryService.cs @@ -23,6 +23,12 @@ namespace MediaBrowser.Controller.Providers IReadOnlyList<string> GetFilePaths(string path, bool clearCache); + /// <summary> + /// Forgets what is cached about a path and about the directory containing it. + /// </summary> + /// <param name="path">The file or directory path that changed.</param> + void Invalidate(string path); + bool IsAccessible(string path); } } |
