fix: retry transient postgres polling saves

This commit is contained in:
2026-04-30 16:05:59 +08:00
parent 39404560cc
commit 9b99cd7e98
2 changed files with 54 additions and 5 deletions
@@ -743,15 +743,40 @@ public sealed class LiveRoomPollingBackgroundService : BackgroundService, ILiveR
await dbContext.SaveChangesAsync(cancellationToken);
return;
}
catch (DbUpdateException ex) when (attempt < 5 && IsTransientDatabaseException(ex))
catch (Exception ex) when (attempt < 5 && IsTransientDatabaseException(ex))
{
await Task.Delay(TimeSpan.FromMilliseconds(300 * Math.Pow(2, attempt - 1)), cancellationToken);
}
}
}
private static bool IsTransientDatabaseException(DbUpdateException exception) =>
exception.InnerException is NpgsqlException npgsqlException && npgsqlException.IsTransient;
private static bool IsTransientDatabaseException(Exception exception)
{
if (exception is DbUpdateException dbUpdateException)
{
return dbUpdateException.InnerException is not null &&
IsTransientDatabaseException(dbUpdateException.InnerException);
}
if (exception is NpgsqlException npgsqlException && npgsqlException.IsTransient)
{
return true;
}
if (exception is TimeoutException or IOException)
{
return true;
}
if (exception is InvalidOperationException invalidOperationException &&
invalidOperationException.Message.Contains("transient failure", StringComparison.OrdinalIgnoreCase))
{
return true;
}
return exception.InnerException is not null &&
IsTransientDatabaseException(exception.InnerException);
}
private static string? Truncate(string? value, int maxLength)
{
+26 -2
View File
@@ -23,6 +23,7 @@ using LiveRecorder.Infrastructure.Services;
using LiveRecorder.WebApi.Middleware;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using Npgsql;
var builder = WebApplication.CreateBuilder(args);
var resetRecordingData = args.Contains("--reset-recording-data", StringComparer.OrdinalIgnoreCase);
@@ -114,9 +115,32 @@ builder.Services.AddHttpClient("bilibili", client =>
var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("ConnectionStrings:DefaultConnection is required.");
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(defaultConnection);
if (connectionStringBuilder.Timeout <= 0)
{
connectionStringBuilder.Timeout = 15;
}
if (connectionStringBuilder.CommandTimeout <= 0)
{
connectionStringBuilder.CommandTimeout = 60;
}
if (connectionStringBuilder.KeepAlive <= 0)
{
connectionStringBuilder.KeepAlive = 30;
}
builder.Services.AddDbContext<LiveRecorderDbContext>(options =>
options.UseNpgsql(defaultConnection, npgsql =>
npgsql.MigrationsAssembly(typeof(LiveRecorderDbContext).Assembly.FullName)));
options.UseNpgsql(connectionStringBuilder.ConnectionString, npgsql =>
{
npgsql.MigrationsAssembly(typeof(LiveRecorderDbContext).Assembly.FullName);
npgsql.CommandTimeout(connectionStringBuilder.CommandTimeout);
npgsql.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorCodesToAdd: null);
}));
builder.Services.AddScoped<IUnitOfWork>(provider => provider.GetRequiredService<LiveRecorderDbContext>());
builder.Services.AddScoped<IAppSettingRepository, AppSettingRepository>();