Skip to content

Commit 1b2dfff

Browse files
authored
Add global lock for migrations to make upgrade more safe with multiple replications (go-gitea#33706)
1 parent b8c2afd commit 1b2dfff

File tree

10 files changed

+46
-18
lines changed

10 files changed

+46
-18
lines changed

cmd/doctor.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package cmd
55

66
import (
7+
"context"
78
"fmt"
89
golog "log"
910
"os"
@@ -130,8 +131,8 @@ func runRecreateTable(ctx *cli.Context) error {
130131
}
131132
recreateTables := migrate_base.RecreateTables(beans...)
132133

133-
return db.InitEngineWithMigration(stdCtx, func(x *xorm.Engine) error {
134-
if err := migrations.EnsureUpToDate(x); err != nil {
134+
return db.InitEngineWithMigration(stdCtx, func(ctx context.Context, x *xorm.Engine) error {
135+
if err := migrations.EnsureUpToDate(ctx, x); err != nil {
135136
return err
136137
}
137138
return recreateTables(x)

cmd/migrate.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import (
77
"context"
88

99
"code.gitea.io/gitea/models/db"
10-
"code.gitea.io/gitea/models/migrations"
1110
"code.gitea.io/gitea/modules/log"
1211
"code.gitea.io/gitea/modules/setting"
12+
"code.gitea.io/gitea/services/versioned_migration"
1313

1414
"github.com/urfave/cli/v2"
1515
)
@@ -36,7 +36,7 @@ func runMigrate(ctx *cli.Context) error {
3636
log.Info("Log path: %s", setting.Log.RootPath)
3737
log.Info("Configuration file: %s", setting.CustomConf)
3838

39-
if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
39+
if err := db.InitEngineWithMigration(context.Background(), versioned_migration.Migrate); err != nil {
4040
log.Fatal("Failed to initialize ORM engine: %v", err)
4141
return err
4242
}

cmd/migrate_storage.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ import (
1313
actions_model "code.gitea.io/gitea/models/actions"
1414
"code.gitea.io/gitea/models/db"
1515
git_model "code.gitea.io/gitea/models/git"
16-
"code.gitea.io/gitea/models/migrations"
1716
packages_model "code.gitea.io/gitea/models/packages"
1817
repo_model "code.gitea.io/gitea/models/repo"
1918
user_model "code.gitea.io/gitea/models/user"
2019
"code.gitea.io/gitea/modules/log"
2120
packages_module "code.gitea.io/gitea/modules/packages"
2221
"code.gitea.io/gitea/modules/setting"
2322
"code.gitea.io/gitea/modules/storage"
23+
"code.gitea.io/gitea/services/versioned_migration"
2424

2525
"github.com/urfave/cli/v2"
2626
)
@@ -227,7 +227,7 @@ func runMigrateStorage(ctx *cli.Context) error {
227227
log.Info("Log path: %s", setting.Log.RootPath)
228228
log.Info("Configuration file: %s", setting.CustomConf)
229229

230-
if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
230+
if err := db.InitEngineWithMigration(context.Background(), versioned_migration.Migrate); err != nil {
231231
log.Fatal("Failed to initialize ORM engine: %v", err)
232232
return err
233233
}

models/db/engine_init.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func UnsetDefaultEngine() {
105105
// When called from the "doctor" command, the migration function is a version check
106106
// that prevents the doctor from fixing anything in the database if the migration level
107107
// is different from the expected value.
108-
func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) {
108+
func InitEngineWithMigration(ctx context.Context, migrateFunc func(context.Context, *xorm.Engine) error) (err error) {
109109
if err = InitEngine(ctx); err != nil {
110110
return err
111111
}
@@ -122,7 +122,7 @@ func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine)
122122
// Installation should only be being re-run if users want to recover an old database.
123123
// However, we should think carefully about should we support re-install on an installed instance,
124124
// as there may be other problems due to secret reinitialization.
125-
if err = migrateFunc(xormEngine); err != nil {
125+
if err = migrateFunc(ctx, xormEngine); err != nil {
126126
return fmt.Errorf("migrate: %w", err)
127127
}
128128

models/migrations/migrations.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ func ExpectedDBVersion() int64 {
413413
}
414414

415415
// EnsureUpToDate will check if the db is at the correct version
416-
func EnsureUpToDate(x *xorm.Engine) error {
416+
func EnsureUpToDate(ctx context.Context, x *xorm.Engine) error {
417417
currentDB, err := GetCurrentDBVersion(x)
418418
if err != nil {
419419
return err

routers/common/db.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"code.gitea.io/gitea/modules/log"
1515
"code.gitea.io/gitea/modules/setting"
1616
"code.gitea.io/gitea/modules/setting/config"
17+
"code.gitea.io/gitea/services/versioned_migration"
1718

1819
"xorm.io/xorm"
1920
)
@@ -41,16 +42,16 @@ func InitDBEngine(ctx context.Context) (err error) {
4142
return nil
4243
}
4344

44-
func migrateWithSetting(x *xorm.Engine) error {
45+
func migrateWithSetting(ctx context.Context, x *xorm.Engine) error {
4546
if setting.Database.AutoMigration {
46-
return migrations.Migrate(x)
47+
return versioned_migration.Migrate(ctx, x)
4748
}
4849

4950
if current, err := migrations.GetCurrentDBVersion(x); err != nil {
5051
return err
5152
} else if current < 0 {
5253
// execute migrations when the database isn't initialized even if AutoMigration is false
53-
return migrations.Migrate(x)
54+
return versioned_migration.Migrate(ctx, x)
5455
} else if expected := migrations.ExpectedDBVersion(); current != expected {
5556
log.Fatal(`"database.AUTO_MIGRATION" is disabled, but current database version %d is not equal to the expected version %d.`+
5657
`You can set "database.AUTO_MIGRATION" to true or migrate manually by running "gitea [--config /path/to/app.ini] migrate"`, current, expected)

routers/install/install.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717

1818
"code.gitea.io/gitea/models/db"
1919
db_install "code.gitea.io/gitea/models/db/install"
20-
"code.gitea.io/gitea/models/migrations"
2120
system_model "code.gitea.io/gitea/models/system"
2221
user_model "code.gitea.io/gitea/models/user"
2322
"code.gitea.io/gitea/modules/auth/password/hash"
@@ -37,6 +36,7 @@ import (
3736
auth_service "code.gitea.io/gitea/services/auth"
3837
"code.gitea.io/gitea/services/context"
3938
"code.gitea.io/gitea/services/forms"
39+
"code.gitea.io/gitea/services/versioned_migration"
4040

4141
"gitea.com/go-chi/session"
4242
)
@@ -359,7 +359,7 @@ func SubmitInstall(ctx *context.Context) {
359359
}
360360

361361
// Init the engine with migration
362-
if err = db.InitEngineWithMigration(ctx, migrations.Migrate); err != nil {
362+
if err = db.InitEngineWithMigration(ctx, versioned_migration.Migrate); err != nil {
363363
db.UnsetDefaultEngine()
364364
ctx.Data["Err_DbSetting"] = true
365365
ctx.RenderWithErr(ctx.Tr("install.invalid_db_setting", err), tplInstall, &form)

services/doctor/dbversion.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"code.gitea.io/gitea/models/db"
1010
"code.gitea.io/gitea/models/migrations"
1111
"code.gitea.io/gitea/modules/log"
12+
"code.gitea.io/gitea/services/versioned_migration"
1213
)
1314

1415
func checkDBVersion(ctx context.Context, logger log.Logger, autofix bool) error {
@@ -21,7 +22,7 @@ func checkDBVersion(ctx context.Context, logger log.Logger, autofix bool) error
2122
logger.Warn("Got Error: %v during ensure up to date", err)
2223
logger.Warn("Attempting to migrate to the latest DB version to fix this.")
2324

24-
err = db.InitEngineWithMigration(ctx, migrations.Migrate)
25+
err = db.InitEngineWithMigration(ctx, versioned_migration.Migrate)
2526
if err != nil {
2627
logger.Critical("Error: %v during migration", err)
2728
}
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package versioned_migration //nolint
5+
6+
import (
7+
"context"
8+
9+
"code.gitea.io/gitea/models/migrations"
10+
"code.gitea.io/gitea/modules/globallock"
11+
12+
"xorm.io/xorm"
13+
)
14+
15+
func Migrate(ctx context.Context, x *xorm.Engine) error {
16+
// only one instance can do the migration at the same time if there are multiple instances
17+
release, err := globallock.Lock(ctx, "gitea_versioned_migration")
18+
if err != nil {
19+
return err
20+
}
21+
defer release()
22+
23+
return migrations.Migrate(x)
24+
}

tests/integration/migration-test/migration_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package migrations
55

66
import (
77
"compress/gzip"
8+
"context"
89
"database/sql"
910
"fmt"
1011
"io"
@@ -247,7 +248,7 @@ func restoreOldDB(t *testing.T, version string) {
247248
}
248249
}
249250

250-
func wrappedMigrate(x *xorm.Engine) error {
251+
func wrappedMigrate(ctx context.Context, x *xorm.Engine) error {
251252
currentEngine = x
252253
return migrations.Migrate(x)
253254
}
@@ -264,15 +265,15 @@ func doMigrationTest(t *testing.T, version string) {
264265

265266
beans, _ := db.NamesToBean()
266267

267-
err = db.InitEngineWithMigration(t.Context(), func(x *xorm.Engine) error {
268+
err = db.InitEngineWithMigration(t.Context(), func(ctx context.Context, x *xorm.Engine) error {
268269
currentEngine = x
269270
return migrate_base.RecreateTables(beans...)(x)
270271
})
271272
assert.NoError(t, err)
272273
currentEngine.Close()
273274

274275
// We do this a second time to ensure that there is not a problem with retained indices
275-
err = db.InitEngineWithMigration(t.Context(), func(x *xorm.Engine) error {
276+
err = db.InitEngineWithMigration(t.Context(), func(ctx context.Context, x *xorm.Engine) error {
276277
currentEngine = x
277278
return migrate_base.RecreateTables(beans...)(x)
278279
})

0 commit comments

Comments
 (0)