fix: retry transient postgres polling saves
This commit is contained in:
@@ -743,15 +743,40 @@ public sealed class LiveRoomPollingBackgroundService : BackgroundService, ILiveR
|
|||||||
await dbContext.SaveChangesAsync(cancellationToken);
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
return;
|
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);
|
await Task.Delay(TimeSpan.FromMilliseconds(300 * Math.Pow(2, attempt - 1)), cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsTransientDatabaseException(DbUpdateException exception) =>
|
private static bool IsTransientDatabaseException(Exception exception)
|
||||||
exception.InnerException is NpgsqlException npgsqlException && npgsqlException.IsTransient;
|
{
|
||||||
|
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)
|
private static string? Truncate(string? value, int maxLength)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ using LiveRecorder.Infrastructure.Services;
|
|||||||
using LiveRecorder.WebApi.Middleware;
|
using LiveRecorder.WebApi.Middleware;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Npgsql;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
var resetRecordingData = args.Contains("--reset-recording-data", StringComparer.OrdinalIgnoreCase);
|
var resetRecordingData = args.Contains("--reset-recording-data", StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -114,9 +115,32 @@ builder.Services.AddHttpClient("bilibili", client =>
|
|||||||
var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection")
|
var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection")
|
||||||
?? throw new InvalidOperationException("ConnectionStrings:DefaultConnection is required.");
|
?? 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 =>
|
builder.Services.AddDbContext<LiveRecorderDbContext>(options =>
|
||||||
options.UseNpgsql(defaultConnection, npgsql =>
|
options.UseNpgsql(connectionStringBuilder.ConnectionString, npgsql =>
|
||||||
npgsql.MigrationsAssembly(typeof(LiveRecorderDbContext).Assembly.FullName)));
|
{
|
||||||
|
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<IUnitOfWork>(provider => provider.GetRequiredService<LiveRecorderDbContext>());
|
||||||
builder.Services.AddScoped<IAppSettingRepository, AppSettingRepository>();
|
builder.Services.AddScoped<IAppSettingRepository, AppSettingRepository>();
|
||||||
|
|||||||
Reference in New Issue
Block a user