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

View File

@@ -5,6 +5,7 @@ using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
namespace BTCPayServer.Data 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>(); var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
builder.AddInterceptors(Data.InvoiceData.MigrationInterceptor.Instance); builder.AddInterceptors(Data.InvoiceData.MigrationInterceptor.Instance);
ConfigureBuilder(builder); ConfigureBuilder(builder, npgsqlOptionsAction);
return new ApplicationDbContext(builder.Options); return new ApplicationDbContext(builder.Options);
} }
} }

View File

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