fix(data): lock racing portfolio publications
This commit is contained in:
@@ -497,9 +497,12 @@ public sealed class PortfolioMigrationPostgresTests(PortfolioMigrationPostgresFi
|
||||
var database = await fixture.CreateMigratedDatabaseAsync();
|
||||
await using var publishConnection = await database.OpenConnectionAsync();
|
||||
await using var rescheduleConnection = await database.OpenConnectionAsync();
|
||||
await using var observerConnection = await database.OpenConnectionAsync();
|
||||
var seed = await SeedCardAsync(publishConnection, isPublic: false);
|
||||
await using var publishTransaction = await publishConnection.BeginTransactionAsync();
|
||||
await using var rescheduleTransaction = await rescheduleConnection.BeginTransactionAsync();
|
||||
var publishPid = await GetBackendPidAsync(publishConnection, publishTransaction);
|
||||
var reschedulePid = await GetBackendPidAsync(rescheduleConnection, rescheduleTransaction);
|
||||
|
||||
await ExecuteNonQueryAsync(
|
||||
publishConnection,
|
||||
@@ -518,14 +521,15 @@ public sealed class PortfolioMigrationPostgresTests(PortfolioMigrationPostgresFi
|
||||
rescheduleTransaction,
|
||||
new NpgsqlParameter("sessionId", seed.SessionIds[0]));
|
||||
|
||||
var commitStates = await Task.WhenAll(
|
||||
CommitAndCaptureSqlStateAsync(publishTransaction),
|
||||
CommitAndCaptureSqlStateAsync(rescheduleTransaction)).WaitAsync(CommandTimeout);
|
||||
var forceRescheduleTriggerTask = ExecuteNonQueryAsync(
|
||||
rescheduleConnection,
|
||||
"SET CONSTRAINTS trg_sessions_unpublish_public_portfolio_games_for_future_reschedule IMMEDIATE",
|
||||
rescheduleTransaction);
|
||||
await WaitUntilBlockedByAsync(observerConnection, reschedulePid, publishPid);
|
||||
|
||||
Assert.True(
|
||||
commitStates[0] is null or PostgresErrorCodes.CheckViolation,
|
||||
$"Unexpected publish SQLSTATE: {commitStates[0] ?? "<none>"}.");
|
||||
Assert.Null(commitStates[1]);
|
||||
Assert.Null(await CommitAndCaptureSqlStateAsync(publishTransaction).WaitAsync(CommandTimeout));
|
||||
await forceRescheduleTriggerTask.WaitAsync(CommandTimeout);
|
||||
await rescheduleTransaction.CommitAsync().WaitAsync(CommandTimeout);
|
||||
|
||||
await using var verificationConnection = await database.OpenConnectionAsync();
|
||||
Assert.False(await ExecuteScalarAsync<bool>(
|
||||
|
||||
@@ -48,6 +48,7 @@ public sealed class PortfolioMigrationTests
|
||||
Assert.Contains("CREATE FUNCTION unpublish_public_portfolio_games_for_future_session() RETURNS TRIGGER LANGUAGE plpgsql", normalizedMigration, StringComparison.Ordinal);
|
||||
Assert.Contains("SELECT s.scheduled_at INTO final_scheduled_at FROM sessions s WHERE s.id = NEW.id;", normalizedMigration, StringComparison.Ordinal);
|
||||
Assert.Contains("IF final_scheduled_at >= now() THEN", normalizedMigration, StringComparison.Ordinal);
|
||||
Assert.Contains("PERFORM pg.id FROM portfolio_games pg WHERE EXISTS", normalizedMigration, StringComparison.Ordinal);
|
||||
Assert.Contains("ORDER BY pg.id FOR UPDATE OF pg;", normalizedMigration, StringComparison.Ordinal);
|
||||
Assert.Contains("UPDATE portfolio_games pg SET is_public = false, updated_at = now() WHERE pg.is_public = true AND EXISTS (SELECT 1 FROM portfolio_game_sessions pgs JOIN sessions s ON s.id = pgs.session_id WHERE pgs.portfolio_game_id = pg.id AND s.scheduled_at >= now());", normalizedMigration, StringComparison.Ordinal);
|
||||
Assert.Contains("CREATE CONSTRAINT TRIGGER trg_sessions_unpublish_public_portfolio_games_for_future_reschedule AFTER UPDATE OF scheduled_at ON sessions DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION unpublish_public_portfolio_games_for_future_session();", normalizedMigration, StringComparison.Ordinal);
|
||||
|
||||
Reference in New Issue
Block a user