Skip to content

Commit

Permalink
Remove migration lock timeout
Browse files Browse the repository at this point in the history
Log migration lock acquisition

Fixes #34196
Fixes #17578
  • Loading branch information
AndriySvyryd committed Aug 13, 2024
1 parent def432c commit 6644f5f
Show file tree
Hide file tree
Showing 46 changed files with 821 additions and 538 deletions.
50 changes: 42 additions & 8 deletions src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public class CosmosDatabaseCreator : IDatabaseCreator
private readonly IDesignTimeModel _designTimeModel;
private readonly IUpdateAdapterFactory _updateAdapterFactory;
private readonly IDatabase _database;
private readonly ICurrentDbContext _currentContext;
private readonly IDbContextOptions _contextOptions;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -29,12 +31,16 @@ public CosmosDatabaseCreator(
ICosmosClientWrapper cosmosClient,
IDesignTimeModel designTimeModel,
IUpdateAdapterFactory updateAdapterFactory,
IDatabase database)
IDatabase database,
ICurrentDbContext currentContext,
IDbContextOptions contextOptions)
{
_cosmosClient = cosmosClient;
_designTimeModel = designTimeModel;
_updateAdapterFactory = updateAdapterFactory;
_database = database;
_currentContext = currentContext;
_contextOptions = contextOptions;
}

/// <summary>
Expand All @@ -55,7 +61,21 @@ public virtual bool EnsureCreated()

if (created)
{
Seed();
InsertData();
}

var coreOptionsExtension =
_contextOptions.FindExtension<CoreOptionsExtension>()
?? new CoreOptionsExtension();

var seed = coreOptionsExtension.Seeder;
if (seed != null)
{
seed(_currentContext.Context, created);
}
else if (coreOptionsExtension.AsyncSeeder != null)
{
throw new InvalidOperationException(CoreStrings.MissingSeeder);
}

return created;
Expand All @@ -81,7 +101,21 @@ public virtual async Task<bool> EnsureCreatedAsync(CancellationToken cancellatio

if (created)
{
await SeedAsync(cancellationToken).ConfigureAwait(false);
await InsertDataAsync(cancellationToken).ConfigureAwait(false);
}

var coreOptionsExtension =
_contextOptions.FindExtension<CoreOptionsExtension>()
?? new CoreOptionsExtension();

var seedAsync = coreOptionsExtension.AsyncSeeder;
if (seedAsync != null)
{
await seedAsync(_currentContext.Context, created, cancellationToken).ConfigureAwait(false);
}
else if (coreOptionsExtension.Seeder != null)
{
throw new InvalidOperationException(CoreStrings.MissingSeeder);
}

return created;
Expand Down Expand Up @@ -153,9 +187,9 @@ private static IEnumerable<ContainerProperties> GetContainersToCreate(IModel mod
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual void Seed()
public virtual void InsertData()
{
var updateAdapter = AddSeedData();
var updateAdapter = AddModelData();

_database.SaveChanges(updateAdapter.GetEntriesToSave());
}
Expand All @@ -166,14 +200,14 @@ public virtual void Seed()
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual Task SeedAsync(CancellationToken cancellationToken = default)
public virtual Task InsertDataAsync(CancellationToken cancellationToken = default)
{
var updateAdapter = AddSeedData();
var updateAdapter = AddModelData();

return _database.SaveChangesAsync(updateAdapter.GetEntriesToSave(), cancellationToken);
}

private IUpdateAdapter AddSeedData()
private IUpdateAdapter AddModelData()
{
var updateAdapter = _updateAdapterFactory.CreateStandalone();
foreach (var entityType in _designTimeModel.Model.GetEntityTypes())
Expand Down
2 changes: 0 additions & 2 deletions src/EFCore.Design/Design/Internal/DbContextOperations.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;
using System.Text;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
Expand Down
3 changes: 1 addition & 2 deletions src/EFCore.Design/Design/Internal/MigrationsOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,7 @@ public virtual void UpdateDatabase(
EnsureServices(services);

var migrator = services.GetRequiredService<IMigrator>();

migrator.Migrate(targetMigration: targetMigration);
migrator.Migrate(targetMigration);
}

_reporter.WriteInformation(DesignStrings.Done);
Expand Down
51 changes: 47 additions & 4 deletions src/EFCore.InMemory/Storage/Internal/InMemoryDatabaseCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,23 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;
public class InMemoryDatabaseCreator : IDatabaseCreator
{
private readonly IDatabase _database;
private readonly ICurrentDbContext _currentContext;
private readonly IDbContextOptions _contextOptions;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public InMemoryDatabaseCreator(IDatabase database)
public InMemoryDatabaseCreator(
IDatabase database,
ICurrentDbContext currentContext,
IDbContextOptions contextOptions)
{
_database = database;
_currentContext = currentContext;
_contextOptions = contextOptions;
}

/// <summary>
Expand Down Expand Up @@ -58,16 +65,52 @@ public virtual Task<bool> EnsureDeletedAsync(CancellationToken cancellationToken
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool EnsureCreated()
=> Database.EnsureDatabaseCreated();
{
var created = Database.EnsureDatabaseCreated();

var coreOptionsExtension =
_contextOptions.FindExtension<CoreOptionsExtension>()
?? new CoreOptionsExtension();

var seed = coreOptionsExtension.Seeder;
if (seed != null)
{
seed(_currentContext.Context, created);
}
else if (coreOptionsExtension.AsyncSeeder != null)
{
throw new InvalidOperationException(CoreStrings.MissingSeeder);
}

return created;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual Task<bool> EnsureCreatedAsync(CancellationToken cancellationToken = default)
=> Task.FromResult(Database.EnsureDatabaseCreated());
public virtual async Task<bool> EnsureCreatedAsync(CancellationToken cancellationToken = default)
{
var created = Database.EnsureDatabaseCreated();

var coreOptionsExtension =
_contextOptions.FindExtension<CoreOptionsExtension>()
?? new CoreOptionsExtension();

var seedAsync = coreOptionsExtension.AsyncSeeder;
if (seedAsync != null)
{
await seedAsync(_currentContext.Context, created, cancellationToken).ConfigureAwait(false);
}
else if (coreOptionsExtension.Seeder != null)
{
throw new InvalidOperationException(CoreStrings.MissingSeeder);
}

return created;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
14 changes: 14 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalEventId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ private enum Id
ColumnOrderIgnoredWarning,
PendingModelChangesWarning,
NonTransactionalMigrationOperationWarning,
AcquiringMigrationLock,

// Query events
QueryClientEvaluationWarning = CoreEventId.RelationalBaseId + 500,
Expand Down Expand Up @@ -749,6 +750,19 @@ private static EventId MakeMigrationsId(Id id)
/// </remarks>
public static readonly EventId NonTransactionalMigrationOperationWarning = MakeMigrationsId(Id.NonTransactionalMigrationOperationWarning);

/// <summary>
/// A migration lock is being acquired.
/// </summary>
/// <remarks>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Migrations" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="EventData" /> payload when used with a <see cref="DiagnosticSource" />.
/// </para>
/// </remarks>
public static readonly EventId AcquiringMigrationLock = MakeMigrationsId(Id.AcquiringMigrationLock);

private static readonly string _queryPrefix = DbLoggerCategory.Query.Name + ".";

private static EventId MakeQueryId(Id id)
Expand Down
30 changes: 30 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2393,6 +2393,36 @@ private static string NonTransactionalMigrationOperationWarning(EventDefinitionB
return d.GenerateMessage(commandText, p.Migration.GetType().ShortDisplayName());
}

/// <summary>
/// Logs for the <see cref="RelationalEventId.AcquiringMigrationLock" /> event.
/// </summary>
/// <param name="diagnostics">The diagnostics logger to use.</param>
public static void AcquiringMigrationLock(
this IDiagnosticsLogger<DbLoggerCategory.Migrations> diagnostics)
{
var definition = RelationalResources.LogAcquiringMigrationLock(diagnostics);

if (diagnostics.ShouldLog(definition))
{
definition.Log(diagnostics);
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new EventData(
definition,
AcquiringMigrationLock);

diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
}
}

private static string AcquiringMigrationLock(EventDefinitionBase definition, EventData payload)
{
var d = (EventDefinition)definition;
return d.GenerateMessage();
}

/// <summary>
/// Logs for the <see cref="RelationalEventId.QueryPossibleUnintendedUseOfEqualsWarning" /> event.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,15 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
[EntityFrameworkInternal]
public EventDefinitionBase? LogNoMigrationsFound;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
public EventDefinitionBase? LogAcquiringMigrationLock;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,6 @@ public static void Migrate(this DatabaseFacade databaseFacade)
/// <param name="targetMigration">
/// The target migration to migrate the database to, or <see langword="null" /> to migrate to the latest.
/// </param>
/// <param name="seed">
/// The optional seed method to run after migrating the database. It will be invoked even if no migrations were applied.
/// </param>
/// <param name="lockTimeout">
/// The maximum amount of time that the migration lock should be held. Unless a catastrophic failure occurs, the
/// lock is released when the migration operation completes.
/// </param>
/// <remarks>
/// <para>
/// Note that this API is mutually exclusive with <see cref="DatabaseFacade.EnsureCreated" />. EnsureCreated does not use migrations
Expand All @@ -145,10 +138,8 @@ public static void Migrate(this DatabaseFacade databaseFacade)
+ " Use a migration bundle or an alternate way of executing migration operations.")]
public static void Migrate(
this DatabaseFacade databaseFacade,
Action<DbContext, IMigratorData>? seed,
string? targetMigration = null,
TimeSpan? lockTimeout = null)
=> databaseFacade.GetRelationalService<IMigrator>().Migrate(seed, targetMigration, lockTimeout);
string? targetMigration)
=> databaseFacade.GetRelationalService<IMigrator>().Migrate(targetMigration);

/// <summary>
/// Asynchronously applies any pending migrations for the context to the database. Will create the database
Expand Down Expand Up @@ -184,13 +175,6 @@ public static Task MigrateAsync(
/// <param name="targetMigration">
/// The target migration to migrate the database to, or <see langword="null" /> to migrate to the latest.
/// </param>
/// <param name="seed">
/// The optional seed method to run after migrating the database. It will be invoked even if no migrations were applied.
/// </param>
/// <param name="lockTimeout">
/// The maximum amount of time that the migration lock should be held. Unless a catastrophic failure occurs, the
/// lock is released when the migration operation completes.
/// </param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <remarks>
/// <para>
Expand All @@ -209,11 +193,9 @@ public static Task MigrateAsync(
+ " Use a migration bundle or an alternate way of executing migration operations.")]
public static Task MigrateAsync(
this DatabaseFacade databaseFacade,
Func<DbContext, IMigratorData, CancellationToken, Task>? seed,
string? targetMigration = null,
TimeSpan? lockTimeout = null,
string? targetMigration,
CancellationToken cancellationToken = default)
=> databaseFacade.GetRelationalService<IMigrator>().MigrateAsync(seed, targetMigration, lockTimeout, cancellationToken);
=> databaseFacade.GetRelationalService<IMigrator>().MigrateAsync(targetMigration, cancellationToken);

/// <summary>
/// Executes the given SQL against the database and returns the number of rows affected.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServi
typeof(IAggregateMethodCallTranslatorPlugin),
new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true)
},
{ typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
{ typeof(IMigratorPlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }
{ typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }
};

/// <summary>
Expand Down
6 changes: 2 additions & 4 deletions src/EFCore.Relational/Migrations/HistoryRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,18 +197,16 @@ protected virtual IReadOnlyList<MigrationCommand> GetCreateCommands()
/// <summary>
/// Gets an exclusive lock on the database.
/// </summary>
/// <param name="timeout">The time to wait for the lock before an exception is thrown.</param>
/// <returns>An object that can be disposed to release the lock.</returns>
public abstract IDisposable GetDatabaseLock(TimeSpan timeout);
public abstract IDisposable GetDatabaseLock();

/// <summary>
/// Gets an exclusive lock on the database.
/// </summary>
/// <param name="timeout">The time to wait for the lock before an exception is thrown.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>An object that can be disposed to release the lock.</returns>
/// <exception cref="OperationCanceledException">If the <see cref="CancellationToken" /> is canceled.</exception>
public abstract Task<IAsyncDisposable> GetDatabaseLockAsync(TimeSpan timeout, CancellationToken cancellationToken = default);
public abstract Task<IAsyncDisposable> GetDatabaseLockAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Configures the entity type mapped to the history table.
Expand Down
6 changes: 2 additions & 4 deletions src/EFCore.Relational/Migrations/IHistoryRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,16 @@ Task<IReadOnlyList<HistoryRow>> GetAppliedMigrationsAsync(
/// <summary>
/// Gets an exclusive lock on the database.
/// </summary>
/// <param name="timeout">The time to wait for the lock before an exception is thrown.</param>
/// <returns>An object that can be disposed to release the lock.</returns>
IDisposable GetDatabaseLock(TimeSpan timeout);
IDisposable GetDatabaseLock();

/// <summary>
/// Gets an exclusive lock on the database.
/// </summary>
/// <param name="timeout">The time to wait for the lock before an exception is thrown.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>An object that can be disposed to release the lock.</returns>
/// <exception cref="OperationCanceledException">If the <see cref="CancellationToken" /> is canceled.</exception>
Task<IAsyncDisposable> GetDatabaseLockAsync(TimeSpan timeout, CancellationToken cancellationToken = default);
Task<IAsyncDisposable> GetDatabaseLockAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Generates a SQL script that will create the history table.
Expand Down
Loading

0 comments on commit 6644f5f

Please sign in to comment.