From dd7c38fb5d1e63b6c71d27482803abcd049814da Mon Sep 17 00:00:00 2001 From: Wendell Jones Date: Wed, 29 Apr 2026 08:12:44 -0400 Subject: [PATCH] Add EnableRetryOnFailure to handle transient connection failures (57P01 terminating connection) --- .../Providers/DirectoryService.cs | 18 ++++- .../PostgresDatabaseProvider.cs | 70 ++----------------- 2 files changed, 23 insertions(+), 65 deletions(-) diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index a29bb9eb..a4f21351 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; using MediaBrowser.Model.IO; @@ -30,7 +31,22 @@ namespace MediaBrowser.Controller.Providers public FileSystemMetadata[] GetFileSystemEntries(string path) { - return _cache.GetOrAdd(path, static (p, fileSystem) => fileSystem.GetFileSystemEntries(p).ToArray(), _fileSystem); + if (_cache.TryGetValue(path, out var result)) + { + return result; + } + + try + { + result = _fileSystem.GetFileSystemEntries(path).ToArray(); + _cache.TryAdd(path, result); + return result; + } + catch (DirectoryNotFoundException) + { + // A folder may be deleted between scans; treat it as empty so refresh logic can continue. + return Array.Empty(); + } } public List GetDirectories(string path) diff --git a/src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs b/src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs index f33acbb1..f98bbcd8 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs +++ b/src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/PostgresDatabaseProvider.cs @@ -277,6 +277,12 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider { npgsqlOptions.MigrationsAssembly(GetType().Assembly.FullName); npgsqlOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + + // Enable automatic retry on transient failures + npgsqlOptions.EnableRetryOnFailure( + maxRetryCount: 3, + maxRetryDelay: TimeSpan.FromSeconds(5), + errorCodesToAdd: new[] { "57P01" }); // Add "terminating connection" to retryable errors }) .ConfigureWarnings(warnings => warnings.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)); @@ -301,7 +307,6 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider // Start with connection string if provided, then override with individual options NpgsqlConnectionStringBuilder connectionBuilder; - if (!string.IsNullOrWhiteSpace(databaseConfiguration.CustomProviderOptions?.ConnectionString)) { // Parse the existing connection string to get all parameters @@ -682,69 +687,6 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider } } - /// - /// Manually records migrations as applied in the migrations history table. - /// This is used to recover from situations where tables were created but the history wasn't updated. - /// - /// The database context. - /// List of migration IDs to record. - /// Cancellation token. - private async Task RecordMigrationAsAppliedAsync(JellyfinDbContext context, IList migrationIds, CancellationToken cancellationToken) - { - var connection = context.Database.GetDbConnection(); - var wasOpened = false; - - try - { - if (connection.State != System.Data.ConnectionState.Open) - { - await connection.OpenAsync(cancellationToken).ConfigureAwait(false); - wasOpened = true; - } - - // Get the current product version from the assembly - var productVersion = typeof(JellyfinDbContext).Assembly - .GetCustomAttribute()? - .InformationalVersion ?? "Unknown"; - - foreach (var migrationId in migrationIds) - { - logger.LogInformation("Recording migration '{MigrationId}' as applied", migrationId); - - var insertQuery = @" - INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") - VALUES (@migrationId, @productVersion) - ON CONFLICT (""MigrationId"") DO NOTHING"; - - await using (var command = connection.CreateCommand()) - { - command.CommandText = insertQuery; - - var migrationParam = command.CreateParameter(); - migrationParam.ParameterName = "@migrationId"; - migrationParam.Value = migrationId; - command.Parameters.Add(migrationParam); - - var versionParam = command.CreateParameter(); - versionParam.ParameterName = "@productVersion"; - versionParam.Value = productVersion; - command.Parameters.Add(versionParam); - - await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); - } - } - - logger.LogInformation("Successfully recorded {Count} migrations in history table", migrationIds.Count); - } - finally - { - if (wasOpened) - { - await connection.CloseAsync().ConfigureAwait(false); - } - } - } - /// public async Task RunScheduledOptimisation(CancellationToken cancellationToken) {