refactor(ecstore): migrate the S3 region into InstanceContext (Phase 5 Slice 4) (#4462)

Phase 5 Slice 4 (backlog#939): move the S3 region — a write-once identity
scalar — out of the GLOBAL_REGION process static into the per-instance
InstanceContext, so two instances can serve different regions.

- InstanceContext gains `region: OnceLock<Region>` with `set_region()` /
  `region()`. `set_region` keeps the write-once fail-fast contract: a second
  write panics, exactly as the process global did (not downgraded to a warn).
- global.rs `set_global_region` / `get_global_region` keep their signatures and
  forward to the current instance's context; the GLOBAL_REGION static is
  removed. Single-instance: startup writes the bootstrap context (which the
  ECStore adopts), so reads are unchanged.

Tests: set/get round-trip, two contexts hold distinct regions, and a second
set_region panics (fail-fast preserved).

Verification: cargo test -p rustfs-ecstore (9 instance-context tests green),
cargo clippy -p rustfs-ecstore --all-targets (clean), make pre-commit (pass).

Refs: backlog#939 (Phase 5, Slice 4). Stacked on Slice 3 (#4417).
This commit is contained in:
Zhengchao An
2026-07-08 17:31:46 +08:00
committed by GitHub
parent 7aa25387ae
commit c138265440
2 changed files with 62 additions and 9 deletions
+56
View File
@@ -41,6 +41,7 @@
use crate::layout::endpoints::SetupType;
use rustfs_lock::{GlobalLockManager, get_global_lock_manager};
use s3s::region::Region;
use std::sync::{Arc, OnceLock};
use tokio::sync::RwLock;
@@ -66,6 +67,11 @@ pub struct InstanceContext {
/// contend). Single-instance aliases the process singleton, so behavior is
/// unchanged; see [`bootstrap_ctx`].
lock_manager: Arc<GlobalLockManager>,
/// This instance's S3 region (Phase 5 Slice 4, backlog#939).
///
/// Write-once like the process global it replaces: startup sets it exactly
/// once and a duplicate write fails fast (see [`InstanceContext::set_region`]).
region: OnceLock<Region>,
}
impl InstanceContext {
@@ -85,6 +91,7 @@ impl InstanceContext {
Self {
erasure_kind: RwLock::new(SetupType::Unknown),
lock_manager,
region: OnceLock::new(),
}
}
@@ -93,6 +100,21 @@ impl InstanceContext {
self.lock_manager.clone()
}
/// Set this instance's S3 region.
///
/// Write-once: panics on a second write, preserving the startup fail-fast
/// contract of the process global it replaces.
pub fn set_region(&self, region: Region) {
self.region
.set(region)
.expect("instance region should be initialized once during startup");
}
/// This instance's S3 region, if it has been set.
pub fn region(&self) -> Option<Region> {
self.region.get().cloned()
}
/// Update this instance's erasure setup type.
pub async fn update_erasure_type(&self, setup_type: SetupType) {
*self.erasure_kind.write().await = setup_type;
@@ -229,4 +251,38 @@ mod tests {
"fresh contexts must not share a lock manager"
);
}
fn region(name: &str) -> Region {
name.parse().expect("valid test region")
}
// A fresh context has no region until set; set-then-get round-trips.
#[tokio::test]
async fn region_set_and_get() {
let ctx = InstanceContext::new();
assert_eq!(ctx.region(), None);
ctx.set_region(region("us-east-1"));
assert_eq!(ctx.region(), Some(region("us-east-1")));
}
// Two contexts hold independent regions.
#[tokio::test]
async fn distinct_contexts_have_distinct_region() {
let ctx_a = InstanceContext::new();
let ctx_b = InstanceContext::new();
ctx_a.set_region(region("us-east-1"));
ctx_b.set_region(region("eu-west-1"));
assert_eq!(ctx_a.region(), Some(region("us-east-1")));
assert_eq!(ctx_b.region(), Some(region("eu-west-1")));
}
// The region is write-once: a second set fails fast, preserving the
// startup contract of the process global it replaced.
#[tokio::test]
#[should_panic(expected = "initialized once")]
async fn set_region_twice_panics() {
let ctx = InstanceContext::new();
ctx.set_region(region("us-east-1"));
ctx.set_region(region("eu-west-1"));
}
}