mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-15 06:02:13 +00:00
fix(multiplayer): repair allocated compose verification
This commit is contained in:
@@ -15,6 +15,32 @@ const migrationTableSQL = `CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
)`
|
||||
|
||||
const migrationLockSQL = `SELECT pg_advisory_xact_lock(hashtext('cosmic-clash:migrations'))`
|
||||
|
||||
// ensureMigrationTable serializes the bootstrap DDL itself. PostgreSQL's
|
||||
// CREATE TABLE IF NOT EXISTS is not safe against concurrent first creation:
|
||||
// the relation-type catalog entry can still collide before either statement
|
||||
// observes the other table. Every long-lived role calls Apply at startup, so
|
||||
// take the same transaction-scoped advisory lock used for individual files
|
||||
// before issuing the bootstrap statement.
|
||||
func ensureMigrationTable(ctx context.Context, db *sql.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("begin migration bootstrap: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
if _, err := tx.ExecContext(ctx, migrationLockSQL); err != nil {
|
||||
return fmt.Errorf("lock migration bootstrap: %w", err)
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, migrationTableSQL); err != nil {
|
||||
return fmt.Errorf("create migration table: %w", err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("commit migration bootstrap: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply executes numbered SQL files in lexical order. A transaction-level
|
||||
// advisory lock serializes concurrent API/worker starts, while each migration
|
||||
// is committed together with its schema_migrations marker so a failed
|
||||
@@ -31,8 +57,8 @@ func Apply(ctx context.Context, db *sql.DB, directory string) error {
|
||||
if len(paths) == 0 {
|
||||
return fmt.Errorf("no migrations found in %s", directory)
|
||||
}
|
||||
if _, err := db.ExecContext(ctx, migrationTableSQL); err != nil {
|
||||
return fmt.Errorf("create migration table: %w", err)
|
||||
if err := ensureMigrationTable(ctx, db); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, path := range paths {
|
||||
version := filepath.Base(path)
|
||||
@@ -50,7 +76,7 @@ func Apply(ctx context.Context, db *sql.DB, directory string) error {
|
||||
_ = tx.Rollback()
|
||||
}
|
||||
}()
|
||||
if _, err := tx.ExecContext(ctx, `SELECT pg_advisory_xact_lock(hashtext('cosmic-clash:migrations'))`); err != nil {
|
||||
if _, err := tx.ExecContext(ctx, migrationLockSQL); err != nil {
|
||||
return fmt.Errorf("lock migration %s: %w", version, err)
|
||||
}
|
||||
var applied bool
|
||||
@@ -86,8 +112,8 @@ func Rollback(ctx context.Context, db *sql.DB, directory string, steps int) erro
|
||||
if db == nil || strings.TrimSpace(directory) == "" || steps <= 0 {
|
||||
return fmt.Errorf("database, migration directory and a positive step count are required")
|
||||
}
|
||||
if _, err := db.ExecContext(ctx, migrationTableSQL); err != nil {
|
||||
return fmt.Errorf("create migration table: %w", err)
|
||||
if err := ensureMigrationTable(ctx, db); err != nil {
|
||||
return err
|
||||
}
|
||||
rows, err := db.QueryContext(ctx, `SELECT version FROM schema_migrations ORDER BY version DESC LIMIT $1`, steps)
|
||||
if err != nil {
|
||||
@@ -123,7 +149,7 @@ func Rollback(ctx context.Context, db *sql.DB, directory string, steps int) erro
|
||||
_ = tx.Rollback()
|
||||
}
|
||||
}()
|
||||
if _, err := tx.ExecContext(ctx, `SELECT pg_advisory_xact_lock(hashtext('cosmic-clash:migrations'))`); err != nil {
|
||||
if _, err := tx.ExecContext(ctx, migrationLockSQL); err != nil {
|
||||
return fmt.Errorf("lock rollback %s: %w", version, err)
|
||||
}
|
||||
var applied bool
|
||||
|
||||
Reference in New Issue
Block a user