mirror of
https://github.com/Lidarr/Lidarr
synced 2026-05-08 12:33:04 +02:00
Fix media parallelism defaults and observability
- Unset/invalid/0: use Environment.ProcessorCount (restore pre-cap behavior) - Drop Lazy cache; re-read env when parallel work runs - Log effective MaxDegree once per process on first disk scan - Update MULTITHREAD_README for NFS opt-in vs default Made-with: Cursor
This commit is contained in:
parent
a98d331b20
commit
c0aec91d50
3 changed files with 28 additions and 14 deletions
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -3,35 +3,34 @@
|
|||
namespace NzbDrone.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<int> MaxDegreeLazy = new Lazy<int>(ReadMaxDegree);
|
||||
|
||||
/// <summary>
|
||||
/// 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 <c>LIDARR_MEDIA_IO_PARALLELISM</c> is unset, empty, invalid, 0, or negative: uses <see cref="Environment.ProcessorCount"/> (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).
|
||||
/// </summary>
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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<string> 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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue