fix: bound bucket metadata migration locking

Use a short dedicated migration lock timeout and fall back to the already loaded legacy or target configuration in memory when persistence is contended. Never fall back to an unlocked metadata save, and keep peer fan-out contexts free of stale lock markers.\n\nRefs: #102

Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-09-02 01:16:10 +08:00
parent 11de51ec26
commit b3a6d5dbf8
2 changed files with 28 additions and 9 deletions
+7 -2
View File
@@ -123,6 +123,7 @@ func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string,
if isMinioMetaBucketName(bucket) { if isMinioMetaBucketName(bucket) {
return updatedAt, errInvalidArgument return updatedAt, errInvalidArgument
} }
notifyCtx := ctx
ctx, unlock, err := lockBucketMetadata(ctx, objAPI, bucket) ctx, unlock, err := lockBucketMetadata(ctx, objAPI, bucket)
if err != nil { if err != nil {
return updatedAt, err return updatedAt, err
@@ -195,7 +196,7 @@ func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string,
if err != nil { if err != nil {
return updatedAt, err return updatedAt, err
} }
globalNotificationSys.LoadBucketMetadata(bgContext(ctx), bucket) // Do not use caller context here globalNotificationSys.LoadBucketMetadata(bgContext(notifyCtx), bucket) // Do not use caller context here
return updatedAt, nil return updatedAt, nil
} }
@@ -228,8 +229,12 @@ func (sys *BucketMetadataSys) saveMetadata(ctx context.Context, objAPI ObjectLay
} }
func lockBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (context.Context, func(), error) { func lockBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (context.Context, func(), error) {
return lockBucketMetadataWithTimeout(ctx, objectAPI, bucket, globalOperationTimeout)
}
func lockBucketMetadataWithTimeout(ctx context.Context, objectAPI ObjectLayer, bucket string, timeout *dynamicTimeout) (context.Context, func(), error) {
lock := objectAPI.NewNSLock(minioMetaBucket, pathJoin(bucketMetaPrefix, bucket, "metadata.lock")) lock := objectAPI.NewNSLock(minioMetaBucket, pathJoin(bucketMetaPrefix, bucket, "metadata.lock"))
lkctx, err := lock.GetLock(ctx, globalOperationTimeout) lkctx, err := lock.GetLock(ctx, timeout)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
+20 -6
View File
@@ -250,10 +250,13 @@ func loadBucketMetadataParse(ctx context.Context, objectAPI ObjectLayer, bucket
if len(configs) > 0 { if len(configs) > 0 {
if !bucketMetadataLockHeld(ctx, bucket) { if !bucketMetadataLockHeld(ctx, bucket) {
return loadBucketMetadataParseUnderLock(ctx, objectAPI, bucket, parse) migrated, lockErr := loadBucketMetadataParseUnderLock(ctx, objectAPI, bucket, parse)
if lockErr == nil {
return migrated, nil
} }
// Old bucket without bucket metadata. Hence we migrate existing settings. internalLogOnceIf(ctx, fmt.Errorf("unable to persist bucket metadata migration for %s, using the legacy configuration in memory: %w", bucket, lockErr), "bucket-metadata-migration-lock-"+bucket)
if err = b.convertLegacyConfigs(ctx, objectAPI, configs); err != nil { b.applyLegacyConfigs(configs)
} else if err = b.convertLegacyConfigs(ctx, objectAPI, configs); err != nil {
return b, err return b, err
} }
} }
@@ -274,7 +277,12 @@ func loadBucketMetadataParse(ctx context.Context, objectAPI ObjectLayer, bucket
// migrate unencrypted remote targets // migrate unencrypted remote targets
if len(b.BucketTargetsConfigJSON) != 0 && GlobalKMS != nil && len(b.BucketTargetsConfigMetaJSON) == 0 && !bucketMetadataLockHeld(ctx, bucket) { if len(b.BucketTargetsConfigJSON) != 0 && GlobalKMS != nil && len(b.BucketTargetsConfigMetaJSON) == 0 && !bucketMetadataLockHeld(ctx, bucket) {
return loadBucketMetadataParseUnderLock(ctx, objectAPI, bucket, parse) migrated, lockErr := loadBucketMetadataParseUnderLock(ctx, objectAPI, bucket, parse)
if lockErr == nil {
return migrated, nil
}
internalLogOnceIf(ctx, fmt.Errorf("unable to persist encrypted bucket target metadata for %s, using the existing configuration in memory: %w", bucket, lockErr), "bucket-metadata-migration-lock-"+bucket)
return b, nil
} }
if err = b.migrateTargetConfig(ctx, objectAPI); err != nil { if err = b.migrateTargetConfig(ctx, objectAPI); err != nil {
return b, err return b, err
@@ -284,7 +292,7 @@ func loadBucketMetadataParse(ctx context.Context, objectAPI ObjectLayer, bucket
} }
func loadBucketMetadataParseUnderLock(ctx context.Context, objectAPI ObjectLayer, bucket string, parse bool) (BucketMetadata, error) { func loadBucketMetadataParseUnderLock(ctx context.Context, objectAPI ObjectLayer, bucket string, parse bool) (BucketMetadata, error) {
ctx, unlock, err := lockBucketMetadata(ctx, objectAPI, bucket) ctx, unlock, err := lockBucketMetadataWithTimeout(ctx, objectAPI, bucket, bucketMetadataMigrationTimeout)
if err != nil { if err != nil {
return newBucketMetadata(bucket), err return newBucketMetadata(bucket), err
} }
@@ -292,6 +300,8 @@ func loadBucketMetadataParseUnderLock(ctx context.Context, objectAPI ObjectLayer
return loadBucketMetadataParse(ctx, objectAPI, bucket, parse) return loadBucketMetadataParse(ctx, objectAPI, bucket, parse)
} }
var bucketMetadataMigrationTimeout = newDynamicTimeout(5*time.Second, time.Second)
// loadBucketMetadata loads and migrates to bucket metadata. // loadBucketMetadata loads and migrates to bucket metadata.
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) { func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
return loadBucketMetadataParse(ctx, objectAPI, bucket, true) return loadBucketMetadataParse(ctx, objectAPI, bucket, true)
@@ -455,7 +465,7 @@ func (b *BucketMetadata) getAllLegacyConfigs(ctx context.Context, objectAPI Obje
return configs, nil return configs, nil
} }
func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI ObjectLayer, configs map[string][]byte) error { func (b *BucketMetadata) applyLegacyConfigs(configs map[string][]byte) {
for legacyFile, configData := range configs { for legacyFile, configData := range configs {
switch legacyFile { switch legacyFile {
case legacyBucketObjectLockEnabledConfigFile: case legacyBucketObjectLockEnabledConfigFile:
@@ -487,6 +497,10 @@ func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI Obj
} }
} }
b.defaultTimestamps() b.defaultTimestamps()
}
func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI ObjectLayer, configs map[string][]byte) error {
b.applyLegacyConfigs(configs)
if err := b.Save(ctx, objectAPI); err != nil { if err := b.Save(ctx, objectAPI); err != nil {
return err return err