From c693da94ceff27143155cb31c2a7fcd1b5c2ea98 Mon Sep 17 00:00:00 2001 From: Attila Szakacs Date: Fri, 17 Jan 2025 10:40:32 +0100 Subject: [PATCH] Fix parallel use of not thread-safe SubtitleFormat instance `SubtitleFormat`'s `LoadSubtitle()` function is not thread-safe. A `SubtitleEditParser` instance's `Parse()` function can be called from multiple threads at the same time. `SubtitleFormat`s are cached in the constructor of each `SubtitleEditParser`, and the same instances are used for each possibly parallel `Parse()` function call, which causes subtitle parse problems. This patch modifies the code, so we only cache the extension -> `SubtitleFormat` type/class mapping and create a new `SubtitleFormat` instance in each `Parse()` call, so no `SubtitleFormat` instance is accessed from multiple threads. Fixes #12113 Kudos for everyone investigating the issue there, most notably @RenV123 for PoC-ing the solution. Signed-off-by: Attila Szakacs --- .../Subtitles/SubtitleEditParser.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs index a79d801fb5..d060b247da 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles public class SubtitleEditParser : ISubtitleParser { private readonly ILogger _logger; - private readonly Dictionary _subtitleFormats; + private readonly Dictionary> _subtitleFormatTypes; /// /// Initializes a new instance of the class. @@ -26,10 +26,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles public SubtitleEditParser(ILogger logger) { _logger = logger; - _subtitleFormats = GetSubtitleFormats() - .Where(subtitleFormat => !string.IsNullOrEmpty(subtitleFormat.Extension)) - .GroupBy(subtitleFormat => subtitleFormat.Extension.TrimStart('.'), StringComparer.OrdinalIgnoreCase) - .ToDictionary(g => g.Key, g => g.ToArray(), StringComparer.OrdinalIgnoreCase); + _subtitleFormatTypes = GetSubtitleFormatTypes(); } /// @@ -38,13 +35,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles var subtitle = new Subtitle(); var lines = stream.ReadAllLines().ToList(); - if (!_subtitleFormats.TryGetValue(fileExtension, out var subtitleFormats)) + if (!_subtitleFormatTypes.TryGetValue(fileExtension, out var subtitleFormatTypesForExtension)) { throw new ArgumentException($"Unsupported file extension: {fileExtension}", nameof(fileExtension)); } - foreach (var subtitleFormat in subtitleFormats) + foreach (var subtitleFormatType in subtitleFormatTypesForExtension) { + var subtitleFormat = (SubtitleFormat)Activator.CreateInstance(subtitleFormatType, true)!; _logger.LogDebug( "Trying to parse '{FileExtension}' subtitle using the {SubtitleFormatParser} format parser", fileExtension, @@ -97,11 +95,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles /// public bool SupportsFileExtension(string fileExtension) - => _subtitleFormats.ContainsKey(fileExtension); + => _subtitleFormatTypes.ContainsKey(fileExtension); - private List GetSubtitleFormats() + private Dictionary> GetSubtitleFormatTypes() { - var subtitleFormats = new List(); + var subtitleFormatTypes = new Dictionary>(StringComparer.OrdinalIgnoreCase); var assembly = typeof(SubtitleFormat).Assembly; foreach (var type in assembly.GetTypes()) @@ -113,9 +111,20 @@ namespace MediaBrowser.MediaEncoding.Subtitles try { - // It shouldn't be null, but the exception is caught if it is - var subtitleFormat = (SubtitleFormat)Activator.CreateInstance(type, true)!; - subtitleFormats.Add(subtitleFormat); + var tempInstance = (SubtitleFormat)Activator.CreateInstance(type, true)!; + var extension = tempInstance.Extension.TrimStart('.'); + if (!string.IsNullOrEmpty(extension)) + { + // Store only the type, we will instantiate from it later + if (!subtitleFormatTypes.TryGetValue(extension, out var subtitleFormatTypesForExtension)) + { + subtitleFormatTypes[extension] = [type]; + } + else + { + subtitleFormatTypesForExtension.Add(type); + } + } } catch (Exception ex) { @@ -123,7 +132,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - return subtitleFormats; + return subtitleFormatTypes; } } }