From 68d5d1d41df4022ed5a560583c0ad1fef95bffd6 Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Sat, 27 Jun 2026 21:35:47 +0800 Subject: [PATCH] refactor: isolate specialized runtime fallbacks (#3958) --- docs/architecture/migration-progress.md | 61 ++++++++++++---- rustfs/src/app/context.rs | 96 ++++++++++--------------- rustfs/src/runtime_sources.rs | 70 +++++++++++++++--- 3 files changed, 144 insertions(+), 83 deletions(-) diff --git a/docs/architecture/migration-progress.md b/docs/architecture/migration-progress.md index 6febe52ff..a09bbbc46 100644 --- a/docs/architecture/migration-progress.md +++ b/docs/architecture/migration-progress.md @@ -5,7 +5,7 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block ## Current Context - Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660) -- Branch: `overtrue/arch-runtime-scalar-resolver-fallback-removal` +- Branch: `overtrue/arch-runtime-specialized-fallback-boundary` - Baseline: completed `C-011/C-012/C-013/API-055/API-059/API-079/API-080/API-081/API-082/API-083/API-084/API-085/API-086/API-087/API-088/API-089/API-090/API-091/API-092/API-093/API-094/API-095/API-096/API-097/API-098/API-099/API-100/API-101/API-102/API-103/API-104/API-105/API-106/API-107/API-108/API-109/API-110/API-111/API-112/API-113/API-114/API-115/API-116/API-117/API-118/API-119/API-120/API-121/API-122/API-123/API-124/API-125/API-126/API-127/API-128/API-129/API-130/API-131/API-132/API-133/API-134/API-135/API-136/API-137/API-138/API-139/API-140/API-141/API-142/API-143/API-144/API-145/API-146/API-147/API-148/API-149/API-150/API-151/API-152/API-153/API-154/API-155/API-156/API-157/API-158/API-159/API-160/API-161/API-162/API-163/API-164/API-165/API-166/API-167/API-168/API-169/API-170/API-171/API-172/API-173/API-174/API-175/API-176/API-177/API-178/API-179/API-180/API-181/API-182/API-183/API-184/API-185/API-186/API-187/API-188/API-189/API-190/API-191/API-192/API-193/API-194/API-195/API-196/API-197/API-198/API-199/API-200/API-201/API-202/API-203/API-204/API-205/API-206/API-207/API-208/API-209/API-210/API-211/API-212/API-213/API-214/API-215/API-216/API-217/API-218/API-219/API-220/API-221/API-222/API-223/API-224/API-225/API-226/API-227/API-228/API-229/API-230/API-231/API-232/API-233/API-234/API-235/API-236/API-237/API-238/API-239/API-240/API-241/API-242/API-243/API-244/API-245/API-246/API-247/API-248/API-249/API-250/API-251/API-252/API-253/API-254/CTX-002`. - Current baseline also includes API-255 from PR #3923, API-256 from PR #3925, CFG-009 from PR #3927, C-007/C-009 from PR #3935, C-008/C-010 @@ -23,20 +23,22 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block from PR #3951, the GLOB-007 optional runtime handle fallback removal from PR #3952, the GLOB-007 AppContext-only fallback signature cleanup from PR #3953, the GLOB-007 runtime resolver fallback boundary cleanup from - PR #3954, and the GLOB-007 service/credential resolver fallback removal from - PR #3955. -- Current phase PR: GLOB-007 scalar/handle resolver fallback removal. -- Based on: `origin/main` after PR #3955 merged. + PR #3954, the GLOB-007 service/credential resolver fallback removal from + PR #3955, and the GLOB-007 scalar/handle resolver fallback removal from + PR #3957. +- Current phase PR: GLOB-007 specialized resolver fallback boundary. +- Based on: `origin/main` after PR #3957 merged. - PR type for this branch: `ci-gate`. -- Runtime behavior changes: when no AppContext is published, scalar and handle - resolvers for outbound TLS generation, daily tier stats, runtime port, - metrics, local node name, tier config, expiry state, and buffer config now - return local empty/default values instead of consulting legacy globals. -- Rust code changes: remove those public no-AppContext read fallbacks while - preserving the default AppContext interface sources used after startup. +- Runtime behavior changes: AppContext specialized resolver helpers for KMS + initialization, notify interface, outbound TLS state, scanner metrics, S3 + Select DB, server-config publish, and storage-class publish now stop at the + AppContext boundary, while root runtime facades preserve the existing + no-AppContext fallback contracts for callers. +- Rust code changes: move those specialized no-AppContext fallbacks out of + `app::context` public resolvers and into the root `runtime_sources` facade. - CI/script changes: none intended. -- Docs changes: update this progress ledger for the scalar/handle fallback - removal. +- Docs changes: update this progress ledger for the specialized fallback + boundary. ## Phase 0 Tasks @@ -2833,6 +2835,10 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block - Current slice: remove the public no-AppContext legacy fallbacks from service/credential resolvers for KMS manager lookup, encryption service, IAM readiness/handle, ready IAM handle, and action credentials. + - Current slice: move specialized KMS initialization, notify interface, + outbound TLS state, scanner metrics, S3 Select DB, server-config publish, + and storage-class publish no-AppContext fallbacks from AppContext resolvers + to root runtime facade wrappers. - Remaining work: remove the next fallback family per PR only after scans prove no production caller depends on it. - Verification: focused RustFS compile and admin test-target compile, @@ -6101,6 +6107,9 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block | Expert | Status | Notes | |---|---|---| +| Quality/architecture | pass | GLOB-007 moves specialized no-AppContext fallback behavior from AppContext public resolvers to root runtime facade wrappers, keeping resolver helpers context-only. | +| Migration preservation | pass | Existing callers keep concrete root facade return contracts for KMS initialization, notify interface, outbound TLS state, scanner metrics, S3 Select DB, server-config publish, and storage-class publish. | +| Testing/verification | pass | Focused resolver, site-replication TLS, S3 Select, and config tests passed; compile, formatting, architecture guard, specialized fallback residual scan, diff hygiene, Rust risk scan, Rust quality scan, and full `make pre-pr` passed before PR. | | Quality/architecture | pass | GLOB-007 removes the scalar/handle fallback family while keeping concrete no-context defaults at the root runtime facade boundary. | | Migration preservation | pass | AppContext-backed behavior is unchanged; absent AppContext no longer consults legacy globals for TLS generation, tier stats, runtime port, metrics, local node name, tier config, expiry state, or buffer config. | | Testing/verification | pass | Focused resolver, site-replication, config, concurrency, and RPC tests passed; compile, formatting, architecture guard, scalar/handle fallback residual scan, diff hygiene, Rust risk scan, Rust quality scan, and full `make pre-pr` passed before PR. | @@ -9813,6 +9822,32 @@ Notes: - `make pre-pr`: passed, including 6917 nextest tests passed, 112 skipped, and doctests passed. +- Issue #660 GLOB-007 specialized resolver fallback boundary: + - `cargo test -p rustfs --lib app::context::tests::resolver_helpers_are_context_first_and_empty_when_context_is_absent`: + passed. + - `cargo test -p rustfs --lib admin::handlers::site_replication::tests::test_site_replication_peer_client_rebuilds_when_generation_changes`: + passed. + - `cargo test -p rustfs --lib admin::handlers::site_replication::tests::test_site_replication_local_endpoint`: + passed, 4 passed. + - `cargo test -p rustfs --lib app::select_object::tests`: passed, 19 + passed. + - `cargo test -p rustfs --lib admin::handlers::config_admin::tests`: passed, + 29 passed. + - `cargo test -p rustfs --lib admin::service::config::tests`: passed, 7 + passed. + - `cargo fmt --all --check`: passed. + - `cargo check -p rustfs --lib`: passed. + - `./scripts/check_architecture_migration_rules.sh`: passed. + - `git diff --check`: passed. + - Specialized fallback residual scan: passed; legacy defaults remain only in + root runtime facade wrappers or AppContext default construction paths. + - Rust risk scan: passed; no new production unwrap/expect, numeric casts, + string error public APIs, boxed public errors, or production + println/eprintln introduced in changed Rust files. Relaxed atomics are + limited to the test-only TLS generation override. + - `make pre-pr`: passed, including 6917 nextest tests passed, 112 skipped, + and doctests passed. + ## Handoff Notes - Continue with larger consumer-migration batches outside the cleaned diff --git a/rustfs/src/app/context.rs b/rustfs/src/app/context.rs index dea7dd309..5ddddfad5 100644 --- a/rustfs/src/app/context.rs +++ b/rustfs/src/app/context.rs @@ -51,9 +51,8 @@ pub fn resolve_kms_runtime_service_manager() -> Option> { } /// Resolve or initialize the KMS runtime service manager using AppContext-first precedence. -pub fn resolve_or_init_kms_runtime_service_manager() -> Arc { +pub fn resolve_or_init_kms_runtime_service_manager() -> Option> { resolve_or_init_kms_runtime_service_manager_with(get_global_app_context()) - .unwrap_or_else(runtime_sources::init_kms_service_manager) } /// Resolve KMS encryption service using AppContext-first precedence. @@ -72,7 +71,7 @@ pub(crate) fn set_test_outbound_tls_generation(generation: u64) { } /// Resolve outbound TLS state using AppContext-first precedence. -pub async fn resolve_outbound_tls_state() -> GlobalPublishedOutboundTlsState { +pub async fn resolve_outbound_tls_state() -> Option { resolve_outbound_tls_state_with(get_global_app_context()).await } @@ -127,16 +126,14 @@ pub fn resolve_object_store_handle_for_context(context: Option<&AppContext>) -> } /// Resolve notify interface using AppContext-first precedence. -pub fn resolve_notify_interface() -> Arc { +pub fn resolve_notify_interface() -> Option> { let context = get_global_app_context(); resolve_notify_interface_for_context(context.as_deref()) } -/// Resolve notify interface using an explicit AppContext, falling back to the legacy global notifier. -pub fn resolve_notify_interface_for_context(context: Option<&AppContext>) -> Arc { - context - .map(|context| context.notify()) - .unwrap_or_else(default_notify_interface) +/// Resolve notify interface using an explicit AppContext. +pub fn resolve_notify_interface_for_context(context: Option<&AppContext>) -> Option> { + context.map(|context| context.notify()) } /// Resolve notification system handle using AppContext-first precedence. @@ -180,12 +177,8 @@ pub fn resolve_daily_tier_stats() -> Option { } /// Resolve scanner metrics report using AppContext-first precedence. -pub async fn resolve_scanner_metrics_report() -> ScannerMetricsReport { - if let Some(report) = resolve_scanner_metrics_report_with(get_global_app_context()).await { - return report; - } - - default_scanner_metrics_interface().report().await +pub async fn resolve_scanner_metrics_report() -> Option { + resolve_scanner_metrics_report_with(get_global_app_context()).await } /// Resolve deployment identity using AppContext-first precedence. @@ -222,12 +215,12 @@ pub fn resolve_internode_metrics() -> Option> { pub async fn resolve_s3select_db( input: SelectObjectContentInput, enable_debug: bool, -) -> QueryResult> { +) -> Option>> { if let Some(context) = get_global_app_context() { - return resolve_s3select_db_with(context, input, enable_debug).await; + return Some(resolve_s3select_db_with(context, input, enable_debug).await); } - default_s3select_db_interface().get(input, enable_debug).await + None } /// Resolve local node name using AppContext-first precedence. @@ -266,15 +259,13 @@ pub fn resolve_server_config_for_context(context: Option<&AppContext>) -> Option } /// Publish server config using AppContext-first precedence. -pub fn publish_server_config(config: Config) { - publish_server_config_with(get_global_app_context(), config, |config| default_server_config_interface().set(config)); +pub fn publish_server_config(config: Config) -> bool { + publish_server_config_with(get_global_app_context(), config) } /// Publish storage class config using AppContext-first precedence. -pub fn publish_storage_class_config(config: StorageClassConfig) { - publish_storage_class_config_with(get_global_app_context(), config, |config| { - default_storage_class_interface().set(config); - }); +pub fn publish_storage_class_config(config: StorageClassConfig) -> bool { + publish_storage_class_config_with(get_global_app_context(), config) } /// Resolve buffer profile config using AppContext-first precedence. @@ -299,12 +290,12 @@ fn resolve_outbound_tls_generation_with(context: Option>) -> Opt context.map(|context| context.outbound_tls_runtime().generation()) } -async fn resolve_outbound_tls_state_with(context: Option>) -> GlobalPublishedOutboundTlsState { +async fn resolve_outbound_tls_state_with(context: Option>) -> Option { if let Some(context) = context { - return context.outbound_tls_runtime().state().await; + return Some(context.outbound_tls_runtime().state().await); } - default_outbound_tls_runtime_interface().state().await + None } fn resolve_iam_ready_with(context: Option>) -> Option { @@ -440,24 +431,22 @@ fn resolve_server_config_with(context: Option>) -> Option>, config: Config, fallback: impl FnOnce(Config)) { +fn publish_server_config_with(context: Option>, config: Config) -> bool { if let Some(context) = context { context.server_config().set(config); - } else { - fallback(config); + return true; } + + false } -fn publish_storage_class_config_with( - context: Option>, - config: StorageClassConfig, - fallback: impl FnOnce(StorageClassConfig), -) { +fn publish_storage_class_config_with(context: Option>, config: StorageClassConfig) -> bool { if let Some(context) = context { context.storage_class().set(config); - } else { - fallback(config); + return true; } + + false } fn resolve_buffer_config_with(context: Option>) -> Option { @@ -904,9 +893,7 @@ mod tests { let context_expiry_state = ExpiryState::new(); let server_config = Config::new(); let context_server_config_published = Arc::new(AtomicUsize::new(0)); - let fallback_server_config_published = Arc::new(AtomicUsize::new(0)); let context_storage_class_published = Arc::new(AtomicUsize::new(0)); - let fallback_storage_class_published = Arc::new(AtomicUsize::new(0)); let buffer_config = RustFSBufferConfig::new(WorkloadProfile::AiTraining); let context_lock_client: Arc = Arc::new(LocalClient::new()); let context_node_name = "context-node".to_string(); @@ -1038,7 +1025,10 @@ mod tests { context_outbound_tls_state.generation ); assert_eq!( - resolve_outbound_tls_state_with(Some(context.clone())).await.generation, + resolve_outbound_tls_state_with(Some(context.clone())) + .await + .expect("context outbound TLS state") + .generation, context_outbound_tls_state.generation ); assert!(resolve_iam_ready_with(Some(context.clone())).expect("context IAM ready")); @@ -1144,18 +1134,10 @@ mod tests { resolve_server_config_with(Some(context.clone())).expect("context server config"), server_config ); - publish_server_config_with(Some(context.clone()), Config::new(), |config| { - drop(config); - fallback_server_config_published.fetch_add(1, Ordering::SeqCst); - }); + assert!(publish_server_config_with(Some(context.clone()), Config::new())); assert_eq!(context_server_config_published.load(Ordering::SeqCst), 1); - assert_eq!(fallback_server_config_published.load(Ordering::SeqCst), 0); - publish_storage_class_config_with(Some(context.clone()), StorageClassConfig::default(), |config| { - drop(config); - fallback_storage_class_published.fetch_add(1, Ordering::SeqCst); - }); + assert!(publish_storage_class_config_with(Some(context.clone()), StorageClassConfig::default())); assert_eq!(context_storage_class_published.load(Ordering::SeqCst), 1); - assert_eq!(fallback_storage_class_published.load(Ordering::SeqCst), 0); assert_eq!( resolve_buffer_config_with(Some(context)) .expect("context buffer config") @@ -1166,10 +1148,12 @@ mod tests { assert!(resolve_kms_runtime_service_manager_with(None).is_none()); assert!(resolve_or_init_kms_runtime_service_manager_with(None).is_none()); assert!(resolve_outbound_tls_generation_with(None).is_none()); + assert!(resolve_outbound_tls_state_with(None).await.is_none()); assert!(resolve_iam_ready_with(None).is_none()); assert!(resolve_iam_handle_with(None).is_none()); assert!(resolve_oidc_handle_with(None).is_none()); assert!(resolve_token_signing_key_with(None).is_none()); + assert!(resolve_notify_interface_for_context(None).is_none()); assert!(!publish_oidc_handle_with(None, context_oidc)); assert!(resolve_bucket_metadata_handle_with(None).is_none()); assert!(resolve_bucket_monitor_handle_with(None).is_none()); @@ -1192,16 +1176,8 @@ mod tests { assert!(resolve_tier_config_handle_with(None).is_none()); assert!(resolve_expiry_state_handle_with(None).is_none()); assert!(resolve_server_config_with(None).is_none()); - publish_server_config_with(None, Config::new(), |config| { - drop(config); - fallback_server_config_published.fetch_add(1, Ordering::SeqCst); - }); - assert_eq!(fallback_server_config_published.load(Ordering::SeqCst), 1); - publish_storage_class_config_with(None, StorageClassConfig::default(), |config| { - drop(config); - fallback_storage_class_published.fetch_add(1, Ordering::SeqCst); - }); - assert_eq!(fallback_storage_class_published.load(Ordering::SeqCst), 1); + assert!(!publish_server_config_with(None, Config::new())); + assert!(!publish_storage_class_config_with(None, StorageClassConfig::default())); assert!(resolve_buffer_config_with(None).is_none()); } } diff --git a/rustfs/src/runtime_sources.rs b/rustfs/src/runtime_sources.rs index 4ceac8462..7cef8ae85 100644 --- a/rustfs/src/runtime_sources.rs +++ b/rustfs/src/runtime_sources.rs @@ -13,35 +13,34 @@ // limitations under the License. use crate::app::context; +use crate::app::storage_api::runtime::{ScannerMetricsReport, StorageClassConfig}; use crate::config::RustFSBufferConfig; use crate::storage_api::server::runtime_sources::{DailyAllTierStats, ExpiryState, TierConfigMgr}; +use rustfs_config::server_config::Config; use rustfs_io_metrics::{PerformanceMetrics, internode_metrics::InternodeMetrics}; +use rustfs_kms::KmsServiceManager; +use rustfs_s3select_api::{QueryResult, server::dbms::DatabaseManagerSystem}; use rustfs_tls_runtime::TlsGeneration; +use s3s::dto::SelectObjectContentInput; use std::sync::Arc; #[cfg(test)] use std::sync::atomic::{AtomicU64, Ordering}; use tokio::sync::RwLock; pub(crate) use context::{ - AppContext, NotifyInterface, publish_oidc_handle, publish_server_config, publish_storage_class_config, - resolve_action_credentials as current_action_credentials, resolve_boot_time as current_boot_time, - resolve_bucket_metadata_handle as current_bucket_metadata_handle, + AppContext, NotifyInterface, publish_oidc_handle, resolve_action_credentials as current_action_credentials, + resolve_boot_time as current_boot_time, resolve_bucket_metadata_handle as current_bucket_metadata_handle, resolve_bucket_monitor_handle as current_bucket_monitor_handle, resolve_deployment_id as current_deployment_id, resolve_encryption_service as current_encryption_service, resolve_endpoints_handle as current_endpoints_handle, resolve_iam_handle as current_iam_handle, resolve_iam_ready as current_iam_ready, resolve_kms_runtime_service_manager as current_kms_runtime_service_manager, resolve_lock_client as current_lock_client, resolve_lock_clients_handle as current_lock_clients_handle, resolve_notification_system as current_notification_system, resolve_notification_system_for_context as current_notification_system_for_context, - resolve_notify_interface as current_notify_interface, - resolve_notify_interface_for_context as current_notify_interface_for_context, resolve_object_store_handle as current_object_store_handle, resolve_object_store_handle_for_context as current_object_store_handle_for_context, - resolve_oidc_handle as current_oidc_handle, - resolve_or_init_kms_runtime_service_manager as current_or_init_kms_runtime_service_manager, - resolve_outbound_tls_state as current_outbound_tls_state, resolve_ready_iam_handle as current_ready_iam_handle, + resolve_oidc_handle as current_oidc_handle, resolve_ready_iam_handle as current_ready_iam_handle, resolve_region as current_region, resolve_replication_pool_handle as current_replication_pool_handle, - resolve_replication_stats_handle as current_replication_stats_handle, resolve_s3select_db as current_s3select_db, - resolve_scanner_metrics_report as current_scanner_metrics_report, resolve_server_config as current_server_config, + resolve_replication_stats_handle as current_replication_stats_handle, resolve_server_config as current_server_config, resolve_server_config_for_context as current_server_config_for_context, resolve_token_signing_key as current_token_signing_key, }; @@ -59,10 +58,30 @@ pub(crate) fn current_app_context() -> Option> { context::get_global_app_context() } +pub(crate) fn current_or_init_kms_runtime_service_manager() -> Arc { + context::resolve_or_init_kms_runtime_service_manager().unwrap_or_else(rustfs_kms::init_global_kms_service_manager) +} + +pub(crate) fn current_notify_interface() -> Arc { + context::resolve_notify_interface().unwrap_or_else(context::default_notify_interface) +} + +pub(crate) fn current_notify_interface_for_context(context: Option<&AppContext>) -> Arc { + context::resolve_notify_interface_for_context(context).unwrap_or_else(context::default_notify_interface) +} + pub(crate) fn current_outbound_tls_generation() -> TlsGeneration { context::resolve_outbound_tls_generation().unwrap_or_else(empty_outbound_tls_generation) } +pub(crate) async fn current_outbound_tls_state() -> rustfs_tls_runtime::GlobalPublishedOutboundTlsState { + if let Some(state) = context::resolve_outbound_tls_state().await { + return state; + } + + context::default_outbound_tls_runtime_interface().state().await +} + #[cfg(test)] fn empty_outbound_tls_generation() -> TlsGeneration { TlsGeneration(TEST_OUTBOUND_TLS_GENERATION.load(Ordering::Relaxed)) @@ -89,6 +108,25 @@ pub(crate) fn current_internode_metrics() -> Arc { context::resolve_internode_metrics().unwrap_or_else(|| Arc::new(InternodeMetrics::default())) } +pub(crate) async fn current_scanner_metrics_report() -> ScannerMetricsReport { + if let Some(report) = context::resolve_scanner_metrics_report().await { + return report; + } + + context::default_scanner_metrics_interface().report().await +} + +pub(crate) async fn current_s3select_db( + input: SelectObjectContentInput, + enable_debug: bool, +) -> QueryResult> { + if let Some(result) = context::resolve_s3select_db(input.clone(), enable_debug).await { + return result; + } + + context::default_s3select_db_interface().get(input, enable_debug).await +} + pub(crate) async fn current_local_node_name() -> String { context::resolve_local_node_name().await.unwrap_or_default() } @@ -104,3 +142,15 @@ pub(crate) fn current_expiry_state_handle() -> Arc> { pub(crate) fn current_buffer_config() -> RustFSBufferConfig { context::resolve_buffer_config().unwrap_or_default() } + +pub(crate) fn publish_server_config(config: Config) { + if !context::publish_server_config(config.clone()) { + context::default_server_config_interface().set(config); + } +} + +pub(crate) fn publish_storage_class_config(config: StorageClassConfig) { + if !context::publish_storage_class_config(config.clone()) { + context::default_storage_class_interface().set(config); + } +}