From f87390e2185fde6b3b6fd6a49e6632bae3d0a448 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 15 Sep 2026 11:17:03 -0400 Subject: Backport pull request #18020 from jellyfin/release-12.z Stop the library monitor from refreshing against a disposed host Original-merge: 3aec32cb249ba54a9b6c0c799d2ff84848069722 Merged-by: crobibero Backported-by: Cody Robibero --- Emby.Server.Implementations/IO/LibraryMonitor.cs | 32 +++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations/IO/LibraryMonitor.cs') diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 0f92e2f03e..e51c863f86 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Library; using MediaBrowser.Controller.Configuration; @@ -40,6 +41,12 @@ namespace Emby.Server.Implementations.IO /// private readonly ConcurrentDictionary _tempIgnoredPaths = new(StringComparer.OrdinalIgnoreCase); + /// + /// Incremented by every so watchers still being created on a background + /// task can tell that the sweep they should have been caught by has already run. + /// + private int _watcherGeneration; + private bool _disposed; /// @@ -69,7 +76,7 @@ namespace Emby.Server.Implementations.IO _dotIgnoreIgnoreRule = dotIgnoreIgnoreRule; appLifetime.ApplicationStarted.Register(Start); - appLifetime.ApplicationStopping.Register(Stop); + appLifetime.ApplicationStopping.Register(Dispose); } /// @@ -120,6 +127,11 @@ namespace Emby.Server.Implementations.IO /// public void Start() { + if (_disposed) + { + return; + } + _libraryManager.ItemAdded += OnLibraryManagerItemAdded; _libraryManager.ItemRemoved += OnLibraryManagerItemRemoved; @@ -233,6 +245,8 @@ namespace Emby.Server.Implementations.IO return; } + var generation = Volatile.Read(ref _watcherGeneration); + // Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel Task.Run(() => { @@ -256,7 +270,11 @@ namespace Emby.Server.Implementations.IO newWatcher.Changed += OnWatcherChanged; newWatcher.Error += OnWatcherError; - if (_fileSystemWatchers.TryAdd(path, newWatcher)) + if (_disposed || Volatile.Read(ref _watcherGeneration) != generation) + { + DisposeWatcher(newWatcher, false); + } + else if (_fileSystemWatchers.TryAdd(path, newWatcher)) { newWatcher.EnableRaisingEvents = true; _logger.LogInformation("Watching directory {Path}", path); @@ -357,6 +375,11 @@ namespace Emby.Server.Implementations.IO { ArgumentException.ThrowIfNullOrEmpty(path); + if (_disposed) + { + return; + } + if (IgnorePatterns.ShouldIgnore(path)) { return; @@ -452,6 +475,8 @@ namespace Emby.Server.Implementations.IO /// public void Stop() { + Interlocked.Increment(ref _watcherGeneration); + _libraryManager.ItemAdded -= OnLibraryManagerItemAdded; _libraryManager.ItemRemoved -= OnLibraryManagerItemRemoved; @@ -496,8 +521,9 @@ namespace Emby.Server.Implementations.IO return; } - Stop(); + // Set before stopping so anything racing us stops handing out new work. _disposed = true; + Stop(); } } } -- cgit v1.2.3