Add EnableRetryOnFailure to handle transient connection failures (57P01 terminating connection)

This commit is contained in:
2026-04-29 08:12:44 -04:00
parent bb309a5120
commit dd7c38fb5d
2 changed files with 23 additions and 65 deletions
@@ -7,6 +7,7 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@@ -30,7 +31,22 @@ namespace MediaBrowser.Controller.Providers
public FileSystemMetadata[] GetFileSystemEntries(string path) 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<FileSystemMetadata>();
}
} }
public List<FileSystemMetadata> GetDirectories(string path) public List<FileSystemMetadata> GetDirectories(string path)
@@ -277,6 +277,12 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider
{ {
npgsqlOptions.MigrationsAssembly(GetType().Assembly.FullName); npgsqlOptions.MigrationsAssembly(GetType().Assembly.FullName);
npgsqlOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); 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 => .ConfigureWarnings(warnings =>
warnings.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)); 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 // Start with connection string if provided, then override with individual options
NpgsqlConnectionStringBuilder connectionBuilder; NpgsqlConnectionStringBuilder connectionBuilder;
if (!string.IsNullOrWhiteSpace(databaseConfiguration.CustomProviderOptions?.ConnectionString)) if (!string.IsNullOrWhiteSpace(databaseConfiguration.CustomProviderOptions?.ConnectionString))
{ {
// Parse the existing connection string to get all parameters // Parse the existing connection string to get all parameters
@@ -682,69 +687,6 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider
} }
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="context">The database context.</param>
/// <param name="migrationIds">List of migration IDs to record.</param>
/// <param name="cancellationToken">Cancellation token.</param>
private async Task RecordMigrationAsAppliedAsync(JellyfinDbContext context, IList<string> 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<System.Reflection.AssemblyInformationalVersionAttribute>()?
.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);
}
}
}
/// <inheritdoc/> /// <inheritdoc/>
public async Task RunScheduledOptimisation(CancellationToken cancellationToken) public async Task RunScheduledOptimisation(CancellationToken cancellationToken)
{ {