diff --git a/cmd/woj/woj.go b/cmd/woj/woj.go index 42fb36a..76975f0 100644 --- a/cmd/woj/woj.go +++ b/cmd/woj/woj.go @@ -37,10 +37,10 @@ func main() { Action: wrap(appServer.RunServer), }, { - Name: "init", - Aliases: []string{"i"}, - Usage: "init database", - Action: wrap(appServer.RunServerInit), + Name: "migrate", + Aliases: []string{"m"}, + Usage: "migrate database", + Action: wrap(appServer.RunServerMigrate), }, { Name: "runner", diff --git a/internal/app/server/server.go b/internal/app/server/server.go index 0ffd98e..163823c 100644 --- a/internal/app/server/server.go +++ b/internal/app/server/server.go @@ -23,7 +23,7 @@ import ( "time" ) -func RunServerInit(i *do.Injector) error { +func RunServerMigrate(i *do.Injector) error { slog := do.MustInvoke[log.Service](i).GetLogger("app.server") // Migrate and shutdown database diff --git a/internal/repo/db/pg.go b/internal/repo/db/pg.go index 75bca9e..5aa7768 100644 --- a/internal/repo/db/pg.go +++ b/internal/repo/db/pg.go @@ -7,11 +7,13 @@ import ( "git.0x7f.app/WOJ/woj-server/internal/misc/config" "git.0x7f.app/WOJ/woj-server/internal/misc/log" "git.0x7f.app/WOJ/woj-server/internal/model" + "git.0x7f.app/WOJ/woj-server/pkg/utils" "github.com/samber/do" "go.uber.org/zap" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/schema" + "hash/fnv" "moul.io/zapgorm2" "time" ) @@ -108,11 +110,34 @@ func (s *service) setup(conf *model.Config) { func (s *service) migrateDatabase() { s.log.Info("Auto Migrating database...") + // Running AutoMigrate concurrently on the same model fails with various race conditions + // https://github.com/go-gorm/gorm/pull/6680 + // https://github.com/go-gorm/postgres/pull/224 + + // Obtain a lock to prevent concurrent AutoMigrate + + lockID := func(s string) int64 { + h := fnv.New64a() + _, err := h.Write([]byte(s)) + return utils.If(err != nil, int64(0x4242AA55), int64(h.Sum64())) + }("gorm:migrator") + + s.err = s.db.Exec("SELECT pg_advisory_lock(?)", lockID).Error + if s.err != nil { + s.log.Error("Failed to obtain lock", zap.Error(s.err)) + return + } + _ = s.db.AutoMigrate(&model.User{}) _ = s.db.AutoMigrate(&model.Problem{}) _ = s.db.AutoMigrate(&model.ProblemVersion{}) _ = s.db.AutoMigrate(&model.Submission{}) _ = s.db.AutoMigrate(&model.Status{}) + + s.err = s.db.Exec("SELECT pg_advisory_unlock(?)", lockID).Error + if s.err != nil { + s.log.Error("Failed to release lock", zap.Error(s.err)) + } } func (s *service) checkAlive(retry int) (*sql.DB, error) {