diff options
8 files changed, 37 insertions, 31 deletions
diff --git a/Directory.Packages.props b/Directory.Packages.props index 89311142c..f9e111b38 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,6 +9,7 @@ <PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" /> <PackageVersion Include="AutoFixture" Version="4.18.1" /> <PackageVersion Include="BDInfo" Version="0.8.0" /> + <PackageVersion Include="BitFaster.Caching" Version="2.5.3" /> <PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.3.4" /> <PackageVersion Include="BlurHashSharp" Version="1.3.4" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" /> @@ -87,4 +88,4 @@ <PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" /> <PackageVersion Include="xunit" Version="2.9.3" /> </ItemGroup> -</Project> +</Project>
\ No newline at end of file diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 8f89f35ac..6722c20da 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -22,6 +22,7 @@ </ItemGroup> <ItemGroup> + <PackageReference Include="BitFaster.Caching" /> <PackageReference Include="DiscUtils.Udf" /> <PackageReference Include="Microsoft.Data.Sqlite" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" /> diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index eb8e31072..62f1f3d3a 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2,7 +2,6 @@ #pragma warning disable CA5394 using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -11,6 +10,7 @@ using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using BitFaster.Caching.Lru; using Emby.Naming.Common; using Emby.Naming.TV; using Emby.Server.Implementations.Library.Resolvers; @@ -64,7 +64,6 @@ namespace Emby.Server.Implementations.Library private const string ShortcutFileExtension = ".mblink"; private readonly ILogger<LibraryManager> _logger; - private readonly ConcurrentDictionary<Guid, BaseItem> _cache; private readonly ITaskManager _taskManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataRepository; @@ -81,6 +80,7 @@ namespace Emby.Server.Implementations.Library private readonly IPeopleRepository _peopleRepository; private readonly ExtraResolver _extraResolver; private readonly IPathManager _pathManager; + private readonly FastConcurrentLru<Guid, BaseItem> _cache; /// <summary> /// The _root folder sync lock. @@ -150,7 +150,9 @@ namespace Emby.Server.Implementations.Library _mediaEncoder = mediaEncoder; _itemRepository = itemRepository; _imageProcessor = imageProcessor; - _cache = new ConcurrentDictionary<Guid, BaseItem>(); + + _cache = new FastConcurrentLru<Guid, BaseItem>(_configurationManager.Configuration.CacheSize); + _namingOptions = namingOptions; _peopleRepository = peopleRepository; _pathManager = pathManager; @@ -158,7 +160,7 @@ namespace Emby.Server.Implementations.Library _configurationManager.ConfigurationUpdated += ConfigurationUpdated; - RecordConfigurationValues(configurationManager.Configuration); + RecordConfigurationValues(_configurationManager.Configuration); } /// <summary> @@ -306,7 +308,7 @@ namespace Emby.Server.Implementations.Library } } - _cache[item.Id] = item; + _cache.AddOrUpdate(item.Id, item); } public void DeleteItem(BaseItem item, DeleteOptions options) @@ -460,14 +462,13 @@ namespace Emby.Server.Implementations.Library item.SetParent(null); _itemRepository.DeleteItem(item.Id); + _cache.TryRemove(item.Id, out _); foreach (var child in children) { _itemRepository.DeleteItem(child.Id); _cache.TryRemove(child.Id, out _); } - _cache.TryRemove(item.Id, out _); - ReportItemRemoved(item, parent); } @@ -1255,7 +1256,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("Guid can't be empty", nameof(id)); } - if (_cache.TryGetValue(id, out BaseItem? item)) + if (_cache.TryGet(id, out var item)) { return item; } @@ -1272,7 +1273,7 @@ namespace Emby.Server.Implementations.Library /// <inheritdoc /> public T? GetItemById<T>(Guid id) - where T : BaseItem + where T : BaseItem { var item = GetItemById(id); if (item is T typedItem) diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 8b88b904b..be1d96bf0 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -1,14 +1,13 @@ #pragma warning disable RS0030 // Do not use banned APIs using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; +using BitFaster.Caching.Lru; using Jellyfin.Database.Implementations; using Jellyfin.Database.Implementations.Entities; -using Jellyfin.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -26,11 +25,9 @@ namespace Emby.Server.Implementations.Library /// </summary> public class UserDataManager : IUserDataManager { - private readonly ConcurrentDictionary<string, UserItemData> _userData = - new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase); - private readonly IServerConfigurationManager _config; private readonly IDbContextFactory<JellyfinDbContext> _repository; + private readonly FastConcurrentLru<string, UserItemData> _cache; /// <summary> /// Initializes a new instance of the <see cref="UserDataManager"/> class. @@ -43,6 +40,7 @@ namespace Emby.Server.Implementations.Library { _config = config; _repository = repository; + _cache = new FastConcurrentLru<string, UserItemData>(Environment.ProcessorCount, _config.Configuration.CacheSize, StringComparer.OrdinalIgnoreCase); } /// <inheritdoc /> @@ -81,7 +79,7 @@ namespace Emby.Server.Implementations.Library var userId = user.InternalId; var cacheKey = GetCacheKey(userId, item.Id); - _userData.AddOrUpdate(cacheKey, userData, (_, _) => userData); + _cache.AddOrUpdate(cacheKey, userData); UserDataSaved?.Invoke(this, new UserDataSaveEventArgs { @@ -182,7 +180,7 @@ namespace Emby.Server.Implementations.Library { var cacheKey = GetCacheKey(user.InternalId, itemId); - if (_userData.TryGetValue(cacheKey, out var data)) + if (_cache.TryGet(cacheKey, out var data)) { return data; } @@ -197,7 +195,7 @@ namespace Emby.Server.Implementations.Library }; } - return _userData.GetOrAdd(cacheKey, data); + return _cache.GetOrAdd(cacheKey, _ => data); } private UserItemData? GetUserDataInternal(Guid userId, Guid itemId, List<string> keys) diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index ba4a2a59c..d8aaf5ba0 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -18,6 +18,7 @@ </PropertyGroup> <ItemGroup> + <PackageReference Include="BitFaster.Caching" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" /> <PackageReference Include="System.Threading.Tasks.Dataflow" /> diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index 474f09dc5..4fca94477 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -1,22 +1,21 @@ #pragma warning disable CS1591 using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using BitFaster.Caching.Lru; using MediaBrowser.Model.IO; namespace MediaBrowser.Controller.Providers { public class DirectoryService : IDirectoryService { - private readonly IFileSystem _fileSystem; - - private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new(StringComparer.Ordinal); + // These caches are primarily used for scanning so no reason to have them be large. + private static readonly FastConcurrentLru<string, FileSystemMetadata[]> _cache = new(Environment.ProcessorCount, Math.Max(128, Environment.ProcessorCount * 10), StringComparer.Ordinal); + private static readonly FastConcurrentLru<string, FileSystemMetadata> _fileCache = new(Environment.ProcessorCount, Math.Max(128, Environment.ProcessorCount * 10), StringComparer.Ordinal); + private static readonly FastConcurrentLru<string, List<string>> _filePathCache = new(Environment.ProcessorCount, Math.Max(128, Environment.ProcessorCount * 10), StringComparer.Ordinal); - private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new(StringComparer.Ordinal); - - private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new(StringComparer.Ordinal); + private readonly IFileSystem _fileSystem; public DirectoryService(IFileSystem fileSystem) { @@ -74,13 +73,13 @@ namespace MediaBrowser.Controller.Providers public FileSystemMetadata? GetFileSystemEntry(string path) { - if (!_fileCache.TryGetValue(path, out var result)) + if (!_fileCache.TryGet(path, out var result)) { var file = _fileSystem.GetFileSystemInfo(path); if (file?.Exists ?? false) { result = file; - _fileCache.TryAdd(path, result); + _fileCache.AddOrUpdate(path, result); } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 693bf90e7..f4e6c8e2c 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -178,6 +178,11 @@ public class ServerConfiguration : BaseApplicationConfiguration public int LibraryUpdateDuration { get; set; } = 30; /// <summary> + /// Gets or sets the maximum amount of items to cache. + /// </summary> + public int CacheSize { get; set; } = Environment.ProcessorCount * 100; + + /// <summary> /// Gets or sets the image saving convention. /// </summary> /// <value>The image saving convention.</value> diff --git a/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs index 07b53bf74..9e7a8c844 100644 --- a/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs +++ b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs @@ -181,8 +181,8 @@ namespace Jellyfin.Controller.Tests fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == path))).Returns(newFileSystemMetadata); var secondResult = directoryService.GetFile(path); - Assert.Equal(cachedFileSystemMetadata, result); - Assert.Equal(cachedFileSystemMetadata, secondResult); + Assert.Equivalent(cachedFileSystemMetadata, result); + Assert.Equivalent(cachedFileSystemMetadata, secondResult); } [Fact] @@ -209,7 +209,7 @@ namespace Jellyfin.Controller.Tests fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths); var directoryService = new DirectoryService(fileSystemMock.Object); - var result = directoryService.GetFilePaths(path); + var result = directoryService.GetFilePaths(path, true); fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths); var secondResult = directoryService.GetFilePaths(path); @@ -241,7 +241,7 @@ namespace Jellyfin.Controller.Tests fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths); var directoryService = new DirectoryService(fileSystemMock.Object); - var result = directoryService.GetFilePaths(path); + var result = directoryService.GetFilePaths(path, true); fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths); var secondResult = directoryService.GetFilePaths(path, true); |
