fix: never record a resident bucket as a CORS load failure

A bucket that is resident keeps its last loaded metadata through a failed
refresh, exactly like every other bucket configuration, so the load-failure
set only ever holds buckets that were never loaded. This makes the CORS
lookup's precedence explicit: resident document first, fail-closed only for
a bucket that has no loaded document.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PvgysXDmhPBBimCReYtA8q
Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-09-03 00:45:02 +08:00
parent f4c1286c9d
commit 94fbb6df6d
2 changed files with 45 additions and 7 deletions
+30
View File
@@ -711,3 +711,33 @@ func testBucketCorsLoadFailedBucketFailsClosed(obj ObjectLayer, _ string, _ stri
t.Fatalf("load-failed CORS lookup performed %d synchronous bucket metadata reads", got)
}
}
// TestBucketCorsResidentConfigSurvivesRefreshFailure: a resident bucket keeps
// its last loaded CORS configuration through a failed refresh, like every
// other bucket configuration, and the failure set never records a resident
// bucket. Only a bucket that was never loaded fails closed.
func TestBucketCorsResidentConfigSurvivesRefreshFailure(t *testing.T) {
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{
t: t,
objAPITest: testBucketCorsResidentConfigSurvivesRefreshFailure,
endpoints: []string{"GetBucketCors"},
})
}
func testBucketCorsResidentConfigSurvivesRefreshFailure(obj ObjectLayer, _ string, bucket string, _ http.Handler, _ auth.Credentials, t *testing.T) {
if _, err := updateLocalBucketCORSMetadata(t.Context(), obj, bucket, []byte(testSiteReplicationCORSDoc)); err != nil {
t.Fatal(err)
}
sys := globalBucketMetadataSys
sys.Lock()
sys.noteLoadFailure(bucket)
_, marked := sys.loadFailed[bucket]
sys.Unlock()
if marked {
t.Fatal("a resident bucket was recorded as a load failure")
}
cfg, _, err := sys.GetResidentCorsConfig(bucket)
if err != nil || cfg == nil {
t.Fatalf("resident CORS configuration lost after a refresh failure: cfg=%v err=%v", cfg, err)
}
}
+15 -7
View File
@@ -51,17 +51,25 @@ type BucketMetadataSys struct {
initialized bool
group *singleflight.Group
metadataMap map[string]BucketMetadata
// loadFailed records real buckets whose metadata could not be loaded at
// startup or during a refresh. They are absent from metadataMap even though
// the subsystem is initialized, and without this bit a resident-only lookup
// could not tell them apart from a name that is not a bucket at all. The
// set is bounded by the number of failed loads and empty in normal operation.
// loadFailed records real buckets whose metadata has never been loaded
// successfully because the startup load or a refresh failed. They are
// absent from metadataMap even though the subsystem is initialized, and
// without this bit a resident-only lookup could not tell them apart from a
// name that is not a bucket at all. It never holds a resident bucket, is
// bounded by the number of failed loads, and is empty in normal operation.
loadFailed map[string]struct{}
}
// noteLoadFailure and clearLoadFailure maintain loadFailed; both expect the
// caller to hold sys.Lock.
func (sys *BucketMetadataSys) noteLoadFailure(bucket string) { sys.loadFailed[bucket] = struct{}{} }
// caller to hold sys.Lock. A bucket that is resident keeps its last loaded
// metadata through a failed refresh, exactly like every other bucket
// configuration, so the set only ever holds non-resident buckets.
func (sys *BucketMetadataSys) noteLoadFailure(bucket string) {
if _, resident := sys.metadataMap[bucket]; !resident {
sys.loadFailed[bucket] = struct{}{}
}
}
func (sys *BucketMetadataSys) clearLoadFailure(bucket string) { delete(sys.loadFailed, bucket) }
// Count returns number of bucket metadata map entries.