Avoid timeouts during EF migrations (#5937)

This commit is contained in:
Nicolas Dorier 2024-04-25 17:27:45 +09:00 committed by GitHub
parent 4e0423cb1e
commit d3277306cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 20 additions and 11 deletions

View File

@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.Options;
using Npgsql;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Operations;
@ -21,7 +22,8 @@ namespace BTCPayServer.Abstractions.Contracts
_migrationTableName = migrationTableName;
}
public abstract T CreateContext();
public T CreateContext() => CreateContext(null);
public abstract T CreateContext(Action<NpgsqlDbContextOptionsBuilder> npgsqlOptionsAction = null);
class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator
{
#pragma warning disable EF1001 // Internal EF Core API usage.
@ -66,16 +68,18 @@ namespace BTCPayServer.Abstractions.Contracts
}
}
public void ConfigureBuilder(DbContextOptionsBuilder builder)
public void ConfigureBuilder(DbContextOptionsBuilder builder) => ConfigureBuilder(builder, null);
public void ConfigureBuilder(DbContextOptionsBuilder builder, Action<NpgsqlDbContextOptionsBuilder> npgsqlOptionsAction = null)
{
builder
.UseNpgsql(_options.Value.ConnectionString, o =>
{
o.EnableRetryOnFailure(10);
o.SetPostgresVersion(12, 0);
var mainSearchPath = GetSearchPath(_options.Value.ConnectionString);
var schemaPrefix = string.IsNullOrEmpty(_migrationTableName) ? "__EFMigrationsHistory" : _migrationTableName;
o.MigrationsHistoryTable(schemaPrefix, mainSearchPath);
npgsqlOptionsAction?.Invoke(o);
var mainSearchPath = GetSearchPath(_options.Value.ConnectionString);
var schemaPrefix = string.IsNullOrEmpty(_migrationTableName) ? "__EFMigrationsHistory" : _migrationTableName;
o.MigrationsHistoryTable(schemaPrefix, mainSearchPath);
})
.ReplaceService<IMigrationsSqlGenerator, CustomNpgsqlMigrationsSqlGenerator>();
}

View File

@ -5,6 +5,7 @@ using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
namespace BTCPayServer.Data
{
@ -14,11 +15,11 @@ namespace BTCPayServer.Data
{
}
public override ApplicationDbContext CreateContext()
public override ApplicationDbContext CreateContext(Action<NpgsqlDbContextOptionsBuilder> npgsqlOptionsAction = null)
{
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
builder.AddInterceptors(Data.InvoiceData.MigrationInterceptor.Instance);
ConfigureBuilder(builder);
ConfigureBuilder(builder, npgsqlOptionsAction);
return new ApplicationDbContext(builder.Options);
}
}

View File

@ -885,21 +885,25 @@ WHERE cte.""Id""=p.""Id""
private async Task Migrate(CancellationToken cancellationToken)
{
using (CancellationTokenSource timeout = new CancellationTokenSource(10_000))
int cancellationTimeout = 60 * 60 * 24;
using (CancellationTokenSource timeout = new CancellationTokenSource(cancellationTimeout))
using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken))
{
retry:
try
{
var db = _DBContextFactory.CreateContext();
await db.Database.MigrateAsync();
_logger.LogInformation("Running the migration scripts...");
var db = _DBContextFactory.CreateContext(o => o.CommandTimeout(cancellationTimeout + 1));
await db.Database.MigrateAsync(timeout.Token);
_logger.LogInformation("All migration scripts ran successfully");
}
// Starting up
catch (ConfigException) { throw; }
catch when (!cts.Token.IsCancellationRequested)
catch (Exception ex) when (!cts.Token.IsCancellationRequested)
{
try
{
_logger.LogWarning(ex, "Error while running migration scripts, retrying...");
await Task.Delay(1000, cts.Token);
}
catch { }