diff --git a/MULTITHREAD_README.md b/MULTITHREAD_README.md index 5be263da0..a88da9fc1 100644 --- a/MULTITHREAD_README.md +++ b/MULTITHREAD_README.md @@ -11,9 +11,9 @@ Parallel import work is **not** limited by Lidarr’s download bandwidth or rate | | | | --- | --- | | **Name** | `LIDARR_MEDIA_IO_PARALLELISM` | -| **Default** | `2` (used when the variable is unset, empty, or not a valid integer) | -| **Allowed** | Integers **1**–**64**; values below **1** fall back to the default | -| **Scope** | Process environment (read once at first use) | +| **Default** | **Unset / empty / invalid / `0`:** same as **`Environment.ProcessorCount`** (original fork behavior). For **NFS or slow storage**, set **`1`** or **`2`** explicitly. | +| **Allowed** | **`1`–`64`:** use that cap. **`0`:** treat as unset (processor count). | +| **Scope** | Process environment (re-read on each scan/import parallel section) | It applies to: @@ -23,6 +23,8 @@ It applies to: **Docker:** fully supported. Set the variable on the container like any other env; the .NET process reads the container environment. +On the first disk scan, Lidarr logs a line like `Media import parallelism: MaxDegreeOfParallelism=…` so you can confirm the value it sees (useful if compose/env typos leave the variable unset). + ### Docker Compose ```yaml @@ -33,7 +35,7 @@ services: - PUID=1000 - PGID=1000 - TZ=Etc/UTC - # Gentle on NFS / network mounts; omit for default (2) + # Gentle on NFS / network mounts (omit var for processor-count default) - LIDARR_MEDIA_IO_PARALLELISM=1 ``` diff --git a/src/NzbDrone.Common/MediaImportParallelism.cs b/src/NzbDrone.Common/MediaImportParallelism.cs index 1100b32c1..057d0f164 100644 --- a/src/NzbDrone.Common/MediaImportParallelism.cs +++ b/src/NzbDrone.Common/MediaImportParallelism.cs @@ -3,35 +3,34 @@ namespace NzbDrone.Common { /// - /// Limits parallel disk work during library scan/import (tag reads, folder scans, candidate scoring). + /// Caps parallel disk work during library scan/import (tag reads, folder scans, candidate scoring), optional via env. /// Unrelated to download bandwidth limits in Lidarr settings. /// public static class MediaImportParallelism { public const string EnvironmentVariableName = "LIDARR_MEDIA_IO_PARALLELISM"; - private const int DefaultMaxDegree = 2; - private const int MinDegree = 1; private const int MaxDegreeCap = 64; - private static readonly Lazy MaxDegreeLazy = new Lazy(ReadMaxDegree); - /// - /// Maximum concurrent workers for scan/import parallelism. Default 2; override with LIDARR_MEDIA_IO_PARALLELISM (1–64). + /// Maximum concurrent workers for scan/import parallelism. + /// If LIDARR_MEDIA_IO_PARALLELISM is unset, empty, invalid, 0, or negative: uses (matches pre-cap fork behavior). + /// Otherwise uses the set value clamped to 1–64. + /// Re-reads the environment each call so container/env changes are visible without restart (same process still needs a new read on next scan). /// - public static int MaxDegreeOfParallelism => MaxDegreeLazy.Value; + public static int MaxDegreeOfParallelism => ReadMaxDegree(); private static int ReadMaxDegree() { var raw = Environment.GetEnvironmentVariable(EnvironmentVariableName); if (string.IsNullOrWhiteSpace(raw) || !int.TryParse(raw.Trim(), out var parsed)) { - return DefaultMaxDegree; + return Math.Max(1, Environment.ProcessorCount); } - if (parsed < MinDegree) + if (parsed <= 0) { - return DefaultMaxDegree; + return Math.Max(1, Environment.ProcessorCount); } return Math.Min(parsed, MaxDegreeCap); diff --git a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs index b1c0f5b71..928518757 100644 --- a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs +++ b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs @@ -5,6 +5,7 @@ using System.IO.Abstractions; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using NLog; using NzbDrone.Common; @@ -48,6 +49,7 @@ public class DiskScanService : private readonly IRootFolderService _rootFolderService; private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; + private static int _mediaParallelismLogged; public DiskScanService(IConfigService configService, IDiskProvider diskProvider, @@ -126,6 +128,17 @@ public void Scan(List folders = null, FilterFilesType filter = FilterFil var musicFilesStopwatch = Stopwatch.StartNew(); + if (Interlocked.CompareExchange(ref _mediaParallelismLogged, 1, 0) == 0) + { + var envRaw = Environment.GetEnvironmentVariable(MediaImportParallelism.EnvironmentVariableName); + _logger.Info( + "Media import parallelism: MaxDegreeOfParallelism={0} ({1}={2}). Set 1–64 to cap IO; unset or 0 uses processor count ({3}).", + MediaImportParallelism.MaxDegreeOfParallelism, + MediaImportParallelism.EnvironmentVariableName, + string.IsNullOrEmpty(envRaw) ? "(unset)" : envRaw, + Environment.ProcessorCount); + } + Parallel.ForEach(foldersToScan, new ParallelOptions { MaxDegreeOfParallelism = MediaImportParallelism.MaxDegreeOfParallelism }, folder => { _logger.ProgressInfo("Scanning {0}", folder);