Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
Expand All @@ -17,21 +18,25 @@ public class OptimizeDatabaseTask : IScheduledTask, IConfigurableScheduledTask
private readonly ILogger<OptimizeDatabaseTask> _logger;
private readonly ILocalizationManager _localization;
private readonly IJellyfinDatabaseProvider _jellyfinDatabaseProvider;
private readonly ILibraryManager _libraryManager;

/// <summary>
/// Initializes a new instance of the <see cref="OptimizeDatabaseTask" /> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
/// <param name="jellyfinDatabaseProvider">Instance of the JellyfinDatabaseProvider that can be used for provider specific operations.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
public OptimizeDatabaseTask(
ILogger<OptimizeDatabaseTask> logger,
ILocalizationManager localization,
IJellyfinDatabaseProvider jellyfinDatabaseProvider)
IJellyfinDatabaseProvider jellyfinDatabaseProvider,
ILibraryManager libraryManager)
{
_logger = logger;
_localization = localization;
_jellyfinDatabaseProvider = jellyfinDatabaseProvider;
_libraryManager = libraryManager;
}

/// <inheritdoc />
Expand Down Expand Up @@ -68,6 +73,15 @@ public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
/// <inheritdoc />
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
// Vacuuming/checkpointing requires an exclusive lock on the database. Running it while a library scan is in
// progress causes both operations to contend for the database and can stall the scan, so defer optimization
// until no scan is running. The task will run again on its next trigger.
if (_libraryManager.IsScanRunning)
{
_logger.LogInformation("Skipping database optimization because a library scan is currently running.");
return;
}

_logger.LogInformation("Optimizing and vacuuming jellyfin.db...");

try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace Emby.Server.Implementations.ScheduledTasks.Tasks;

Expand All @@ -20,18 +21,21 @@ public class PeopleValidationTask : IScheduledTask, IConfigurableScheduledTask
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
private readonly IDbContextFactory<JellyfinDbContext> _dbContextFactory;
private readonly ILogger<PeopleValidationTask> _logger;

/// <summary>
/// Initializes a new instance of the <see cref="PeopleValidationTask" /> class.
/// </summary>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
/// <param name="dbContextFactory">Instance of the <see cref="IDbContextFactory{TContext}"/> interface.</param>
public PeopleValidationTask(ILibraryManager libraryManager, ILocalizationManager localization, IDbContextFactory<JellyfinDbContext> dbContextFactory)
/// <param name="logger">Instance of the <see cref="ILogger{TCategoryName}"/> interface.</param>
public PeopleValidationTask(ILibraryManager libraryManager, ILocalizationManager localization, IDbContextFactory<JellyfinDbContext> dbContextFactory, ILogger<PeopleValidationTask> logger)
{
_libraryManager = libraryManager;
_localization = localization;
_dbContextFactory = dbContextFactory;
_logger = logger;
}

/// <inheritdoc />
Expand Down Expand Up @@ -71,6 +75,14 @@ public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
/// <inheritdoc />
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
// People validation performs heavy database writes that contend with an active library scan.
// Defer it until the scan has finished; the task will run again on its next trigger.
if (_libraryManager.IsScanRunning)
{
Comment thread
JPVenson marked this conversation as resolved.
_logger.LogInformation("Skipping people validation because a library scan is currently running.");
return;
}

IProgress<double> subProgress = new Progress<double>((val) => progress.Report(val / 2));
await _libraryManager.ValidatePeopleAsync(subProgress, cancellationToken).ConfigureAwait(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Jellyfin.Server.Implementations.StorageHelpers;
using Jellyfin.Server.Implementations.SystemBackupService;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.SystemBackupService;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
Expand All @@ -33,6 +34,7 @@ public class BackupService : IBackupService
private readonly IServerApplicationPaths _applicationPaths;
private readonly IJellyfinDatabaseProvider _jellyfinDatabaseProvider;
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly ILibraryManager _libraryManager;
private static readonly JsonSerializerOptions _serializerSettings = new JsonSerializerOptions(JsonSerializerDefaults.General)
{
AllowTrailingCommas = true,
Expand All @@ -50,20 +52,23 @@ public class BackupService : IBackupService
/// <param name="applicationPaths">The application paths.</param>
/// <param name="jellyfinDatabaseProvider">The Jellyfin database Provider in use.</param>
/// <param name="applicationLifetime">The SystemManager.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
public BackupService(
ILogger<BackupService> logger,
IDbContextFactory<JellyfinDbContext> dbProvider,
IServerApplicationHost applicationHost,
IServerApplicationPaths applicationPaths,
IJellyfinDatabaseProvider jellyfinDatabaseProvider,
IHostApplicationLifetime applicationLifetime)
IHostApplicationLifetime applicationLifetime,
ILibraryManager libraryManager)
{
_logger = logger;
_dbProvider = dbProvider;
_applicationHost = applicationHost;
_applicationPaths = applicationPaths;
_jellyfinDatabaseProvider = jellyfinDatabaseProvider;
_hostApplicationLifetime = applicationLifetime;
_libraryManager = libraryManager;
}

/// <inheritdoc/>
Expand Down Expand Up @@ -263,6 +268,14 @@ private bool TestBackupVersionCompatibility(Version backupEngineVersion)
/// <inheritdoc/>
public async Task<BackupManifestDto> CreateBackupAsync(BackupOptionsDto backupOptions)
{
// Creating a backup runs a database optimization and reads the entire database under a transaction, both of
// which heavily contend with an active library scan and could capture an inconsistent database state.
if (_libraryManager.IsScanRunning)
{
_logger.LogWarning("Cannot create a backup while a library scan is running.");
throw new InvalidOperationException("Cannot create a backup while a library scan is running. Please try again once the scan has finished.");
}

var manifest = new BackupManifest()
{
DateCreated = DateTime.UtcNow,
Expand Down
Loading