From 66eff8b9ca18ab1cde76196c145ad0a98d0eda99 Mon Sep 17 00:00:00 2001 From: Hannes Braun Date: Mon, 16 Jan 2023 18:06:44 +0100 Subject: [PATCH] Allow limiting parallel image encodings to reduce memory usage (#8783) --- .../Configuration/ServerConfiguration.cs | 6 ++++ src/Jellyfin.Drawing/ImageProcessor.cs | 31 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index d3e042abaa..c39162250a 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -259,5 +259,11 @@ namespace MediaBrowser.Model.Configuration /// /// The chapter image resolution. public ImageResolution ChapterImageResolution { get; set; } = ImageResolution.MatchSource; + + /// + /// Gets or sets the limit for parallel image encoding. + /// + /// The limit for parallel image encoding. + public int ParallelImageEncodingLimit { get; set; } = 0; } } diff --git a/src/Jellyfin.Drawing/ImageProcessor.cs b/src/Jellyfin.Drawing/ImageProcessor.cs index b381c9ae73..353a27b254 100644 --- a/src/Jellyfin.Drawing/ImageProcessor.cs +++ b/src/Jellyfin.Drawing/ImageProcessor.cs @@ -5,10 +5,12 @@ using System.IO; using System.Linq; using System.Net.Mime; using System.Text; +using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.MediaEncoding; @@ -38,6 +40,8 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable private readonly IImageEncoder _imageEncoder; private readonly IMediaEncoder _mediaEncoder; + private readonly SemaphoreSlim _parallelEncodingLimit; + private bool _disposed; /// @@ -48,18 +52,28 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable /// The filesystem. /// The image encoder. /// The media encoder. + /// The configuration. public ImageProcessor( ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IImageEncoder imageEncoder, - IMediaEncoder mediaEncoder) + IMediaEncoder mediaEncoder, + IServerConfigurationManager config) { _logger = logger; _fileSystem = fileSystem; _imageEncoder = imageEncoder; _mediaEncoder = mediaEncoder; _appPaths = appPaths; + + var semaphoreCount = config.Configuration.ParallelImageEncodingLimit; + if (semaphoreCount < 1) + { + semaphoreCount = 2 * Environment.ProcessorCount; + } + + _parallelEncodingLimit = new(semaphoreCount, semaphoreCount); } private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images"); @@ -199,7 +213,18 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable { if (!File.Exists(cacheFilePath)) { - string resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); + // Limit number of parallel (more precisely: concurrent) image encodings to prevent a high memory usage + await _parallelEncodingLimit.WaitAsync().ConfigureAwait(false); + + string resultPath; + try + { + resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); + } + finally + { + _parallelEncodingLimit.Release(); + } if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) { @@ -563,6 +588,8 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable disposable.Dispose(); } + _parallelEncodingLimit?.Dispose(); + _disposed = true; } }