From 468fd81a8a781b73c0aef5a64464193c85d91409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=AD=A3=E8=B6=85?= Date: Sun, 21 Jun 2026 01:56:27 +0800 Subject: [PATCH] refactor: move startup app context owner (#3670) --- docs/architecture/migration-progress.md | 47 ++++++++-- rustfs/src/app/context.rs | 1 + rustfs/src/app/context/startup.rs | 109 ++++++++++++++++++++++++ rustfs/src/startup_iam.rs | 26 +----- scripts/layer-dependency-baseline.txt | 2 - 5 files changed, 155 insertions(+), 30 deletions(-) create mode 100644 rustfs/src/app/context/startup.rs diff --git a/docs/architecture/migration-progress.md b/docs/architecture/migration-progress.md index cf958f377..4070a3fd4 100644 --- a/docs/architecture/migration-progress.md +++ b/docs/architecture/migration-progress.md @@ -2460,6 +2460,30 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block migration/layer guards, formatting, diff hygiene, Rust risk scan, branch freshness check, pre-commit quality gate, and three-expert review. +- [x] `R-054` Move startup AppContext bootstrap owner into app context. + - Do: move the post-IAM AppContext bootstrap helper out of IAM startup and + into the app context owner while keeping IAM startup on the existing + context boundary. + - Acceptance: inline startup and deferred IAM recovery still initialize or + reuse the global AppContext through one owner. + - Must preserve: global AppContext singleton behavior, IAM handle lookup, + KMS handle wiring, startup error mapping, and readiness ordering. + - Verification: focused startup checks, RustFS lib check, migration/layer + guards, formatting, diff hygiene, Rust risk scan, branch freshness check, + pre-commit quality gate, and three-expert review. + +- [x] `R-055` Retire stale startup IAM layer baseline entries. + - Do: remove the old direct `get_global_app_context` and + `init_global_app_context` startup IAM baseline entries after the app + context owner absorbs those calls. + - Acceptance: layer dependency guard reports no new reverse dependencies and + no stale baseline entries. + - Must preserve: the existing accepted startup-to-AppContext boundary, + AppContext initialization semantics, and no new layer cycles. + - Verification: focused startup checks, RustFS lib check, migration/layer + guards, formatting, diff hygiene, Rust risk scan, branch freshness check, + pre-commit quality gate, and three-expert review. + - [x] `E-001/E-SET-001` Add ECStore layout skeleton and set-layout boundary. - Do: create the ECStore internal layout ownership buckets and pin static set layout versus runtime `Sets`/`SetDisks` orchestration boundaries before any @@ -2702,21 +2726,34 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block ## Next PRs -1. `pure-move`: continue the context-first runtime startup path after the - embedded startup ownership slices land. +1. `pure-move`: continue the context-first runtime startup path by narrowing + remaining startup service ownership boundaries. ## Pre-Push Review Log | Expert | Status | Notes | |---|---|---| -| Quality/architecture | passed | R-052 and R-053 make AppContext bootstrap outcome explicit and share inline/recovery finalization without widening public API. | -| Migration preservation | passed | IAM init order, singleton reuse, KMS/IAM handles, degraded recovery, backoff/shutdown token, readiness stages, and log behavior stay unchanged. | -| Testing/verification | passed | Focused startup IAM checks, RustFS lib check, architecture/layer/unsafe guards, formatting, diff hygiene, Rust risk scan, and pre-commit gate passed. | +| Quality/architecture | passed | R-054 and R-055 move AppContext bootstrap ownership into app context and shrink stale layer baseline entries without widening public API. | +| Migration preservation | passed | IAM init order, singleton reuse, KMS handle wiring, degraded recovery, readiness stages, and log behavior stay unchanged. | +| Testing/verification | passed | Focused startup checks, RustFS lib check, architecture/layer/unsafe guards, formatting, diff hygiene, Rust risk scan, and pre-commit gate passed. | ## Verification Notes Passed before push: +- Issue #660 R-054/R-055 current slice: + - `cargo test -p rustfs --lib startup_ -- --nocapture`: passed; 51 tests. + - `cargo check -p rustfs --lib`: passed. + - `cargo fmt --all --check`: passed. + - `git diff --check`: passed. + - `./scripts/check_architecture_migration_rules.sh`: passed. + - `./scripts/check_layer_dependencies.sh`: passed. + - `./scripts/check_unsafe_code_allowances.sh`: passed. + - Rust risk scan on changed Rust files: passed; only a test-only `expect` + call was present. + - `make pre-commit`: passed; nextest ran 6339 tests with 6339 passed, 111 + skipped, and doctests passed. + - Issue #660 R-052/R-053 current slice: - `cargo test -p rustfs --lib startup_iam -- --nocapture`: passed. - `cargo check -p rustfs --lib`: passed. diff --git a/rustfs/src/app/context.rs b/rustfs/src/app/context.rs index badc6f85b..f74872c75 100644 --- a/rustfs/src/app/context.rs +++ b/rustfs/src/app/context.rs @@ -20,6 +20,7 @@ mod compat; mod global; mod handles; mod interfaces; +mod startup; pub use compat::*; pub use global::*; diff --git a/rustfs/src/app/context/startup.rs b/rustfs/src/app/context/startup.rs new file mode 100644 index 000000000..2d78bd98e --- /dev/null +++ b/rustfs/src/app/context/startup.rs @@ -0,0 +1,109 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::global::{AppContext, get_global_app_context, init_global_app_context}; +use crate::app::storage_compat::ECStore; +use rustfs_kms::KmsServiceManager; +use std::io::{Error, Result}; +use std::sync::Arc; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum StartupAppContextBootstrap { + AlreadyAvailable, + Initialized, +} + +impl AppContext { + pub(crate) fn ensure_startup_after_iam(store: Arc, kms_interface: Arc) -> Result<()> { + ensure_startup_app_context_after_iam_with( + || get_global_app_context().is_some(), + || { + let iam_interface = rustfs_iam::get().map_err(|_| Error::other("IAM is initialized but unavailable"))?; + init_global_app_context(AppContext::with_default_interfaces(store, iam_interface, kms_interface)); + Ok(()) + }, + )?; + Ok(()) + } +} + +fn ensure_startup_app_context_after_iam_with( + is_available: IsAvailable, + init_context: InitContext, +) -> Result +where + IsAvailable: FnOnce() -> bool, + InitContext: FnOnce() -> Result<()>, +{ + if is_available() { + return Ok(StartupAppContextBootstrap::AlreadyAvailable); + } + + init_context()?; + Ok(StartupAppContextBootstrap::Initialized) +} + +#[cfg(test)] +mod tests { + use super::{StartupAppContextBootstrap, ensure_startup_app_context_after_iam_with}; + use std::io::Error; + use std::sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }; + + #[test] + fn startup_app_context_bootstrap_reuses_existing_context() { + let init_calls = Arc::new(AtomicUsize::new(0)); + let init_calls_for_assert = init_calls.clone(); + + let disposition = ensure_startup_app_context_after_iam_with( + || true, + move || { + init_calls.fetch_add(1, Ordering::SeqCst); + Ok(()) + }, + ) + .expect("existing app context should be reused"); + + assert_eq!(disposition, StartupAppContextBootstrap::AlreadyAvailable); + assert_eq!(init_calls_for_assert.load(Ordering::SeqCst), 0); + } + + #[test] + fn startup_app_context_bootstrap_initializes_missing_context() { + let init_calls = Arc::new(AtomicUsize::new(0)); + let init_calls_for_assert = init_calls.clone(); + + let disposition = ensure_startup_app_context_after_iam_with( + || false, + move || { + init_calls.fetch_add(1, Ordering::SeqCst); + Ok(()) + }, + ) + .expect("missing app context should initialize"); + + assert_eq!(disposition, StartupAppContextBootstrap::Initialized); + assert_eq!(init_calls_for_assert.load(Ordering::SeqCst), 1); + } + + #[test] + fn startup_app_context_bootstrap_returns_init_error() { + let err = ensure_startup_app_context_after_iam_with(|| false, || Err(Error::other("iam unavailable"))) + .expect_err("init failure should be returned"); + + assert_eq!(err.to_string(), "iam unavailable"); + } +} diff --git a/rustfs/src/startup_iam.rs b/rustfs/src/startup_iam.rs index 547db5746..8631f6bef 100644 --- a/rustfs/src/startup_iam.rs +++ b/rustfs/src/startup_iam.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::app::context::{AppContext, get_global_app_context, init_global_app_context}; +use crate::app::context::AppContext; use crate::server::{ServiceStateManager, publish_ready_when_runtime_ready}; use crate::storage_compat::ECStore; use rustfs_common::{GlobalReadiness, SystemStage}; @@ -44,12 +44,6 @@ pub enum IamBootstrapDisposition { Deferred, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum AppContextBootstrapDisposition { - AlreadyAvailable, - Initialized, -} - pub async fn publish_ready_for_iam_bootstrap( disposition: IamBootstrapDisposition, readiness: &GlobalReadiness, @@ -77,27 +71,13 @@ where Ok(false) } -fn ensure_app_context_after_iam( - store: Arc, - kms_interface: Arc, -) -> Result { - if get_global_app_context().is_some() { - return Ok(AppContextBootstrapDisposition::AlreadyAvailable); - } - - let iam_interface = rustfs_iam::get().map_err(|_| std::io::Error::other("IAM is initialized but unavailable"))?; - - init_global_app_context(AppContext::with_default_interfaces(store, iam_interface, kms_interface)); - Ok(AppContextBootstrapDisposition::Initialized) -} - async fn finalize_iam_recovery( store: Arc, kms_interface: Arc, readiness: Arc, state_manager: Option>, ) -> Result<()> { - ensure_app_context_after_iam(store, kms_interface)?; + AppContext::ensure_startup_after_iam(store, kms_interface)?; readiness.mark_stage(SystemStage::IamReady); publish_ready_when_runtime_ready(readiness.as_ref(), state_manager.as_deref()).await @@ -335,7 +315,7 @@ pub async fn bootstrap_or_defer_iam_init( ) -> Result { match attempt_init_iam_sys(store.clone()).await { Ok(()) => { - ensure_app_context_after_iam(store, kms_interface)?; + AppContext::ensure_startup_after_iam(store, kms_interface)?; readiness.mark_stage(SystemStage::IamReady); return Ok(IamBootstrapDisposition::ReadyInline); } diff --git a/scripts/layer-dependency-baseline.txt b/scripts/layer-dependency-baseline.txt index dc78fa8c9..785f644bb 100644 --- a/scripts/layer-dependency-baseline.txt +++ b/scripts/layer-dependency-baseline.txt @@ -45,8 +45,6 @@ accepted|rustfs/src/server/layer.rs|infra->interface|crate::admin::console::is_c accepted|rustfs/src/server/layer.rs|infra->interface|crate::admin::handlers::health::HealthProbe|HTTP layer builds health responses accepted|rustfs/src/server/layer.rs|infra->interface|crate::admin::handlers::health::build_health_response_parts|HTTP layer builds health responses accepted|rustfs/src/startup_iam.rs|infra->app|crate::app::context::AppContext|startup wires IAM bootstrap through AppContext -accepted|rustfs/src/startup_iam.rs|infra->app|crate::app::context::get_global_app_context|startup wires IAM bootstrap through AppContext -accepted|rustfs/src/startup_iam.rs|infra->app|crate::app::context::init_global_app_context|startup wires IAM bootstrap through AppContext accepted|rustfs/src/storage/ecfs_extend.rs|infra->interface|crate::storage::ecfs::ListObjectUnorderedQuery|storage extension uses current ECFS interface query type accepted|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::ecfs::FS|storage tests exercise current ECFS interface path accepted|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::ecfs::validate_object_lock_configuration_input|storage tests exercise current ECFS object-lock validator