From 57260c8314a46060f9368e146ecbe6b231155dc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=AD=A3=E8=B6=85?= Date: Mon, 15 Jun 2026 14:55:33 +0800 Subject: [PATCH] refactor: centralize startup command bootstrap (#3471) --- crates/obs/src/logging.rs | 18 +-- docs/architecture/migration-progress.md | 72 +++++++---- docs/architecture/startup-timeline.md | 37 +++--- rustfs/src/lib.rs | 1 + rustfs/src/main.rs | 137 +-------------------- rustfs/src/startup_entrypoint.rs | 155 ++++++++++++++++++++++++ 6 files changed, 232 insertions(+), 188 deletions(-) create mode 100644 rustfs/src/startup_entrypoint.rs diff --git a/crates/obs/src/logging.rs b/crates/obs/src/logging.rs index 8786d1e5c..eca620ded 100644 --- a/crates/obs/src/logging.rs +++ b/crates/obs/src/logging.rs @@ -160,19 +160,21 @@ mod tests { #[test] fn startup_runtime_logging_does_not_dump_full_config_debug_output() { - let path = workspace_root().join("rustfs/src/main.rs"); - let source = fs::read_to_string(&path).unwrap_or_else(|err| panic!("failed to read {}: {}", path.display(), err)); - assert!( - !source.contains("debug!(\"config: {:?}\", &config)"), - "found forbidden full config debug output in {}", - path.display() - ); + for relative_path in ["rustfs/src/main.rs", "rustfs/src/startup_entrypoint.rs"] { + let path = workspace_root().join(relative_path); + let source = fs::read_to_string(&path).unwrap_or_else(|err| panic!("failed to read {}: {}", path.display(), err)); + assert!( + !source.contains("debug!(\"config: {:?}\", &config)"), + "found forbidden full config debug output in {}", + path.display() + ); + } } #[test] fn startup_fatal_stderr_uses_single_formatter_for_pre_observability_failures() { assert_source_contains( - "rustfs/src/main.rs", + "rustfs/src/startup_entrypoint.rs", &[ "fn format_fatal_stderr_message(context: &str, error: impl std::fmt::Display) -> String", "fn emit_fatal_stderr(context: &str, error: impl std::fmt::Display)", diff --git a/docs/architecture/migration-progress.md b/docs/architecture/migration-progress.md index 40e4d6c99..9bea1fd68 100644 --- a/docs/architecture/migration-progress.md +++ b/docs/architecture/migration-progress.md @@ -5,17 +5,18 @@ 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-startup-ready-shutdown-runtime` -- Baseline: `origin/main` at `e26eea7f3a5ef6c272a322a47f58f5a10cb2342b0` +- Branch: `overtrue/arch-startup-command-bootstrap` +- Baseline: `origin/main` at `941b8afee8619c6a038c20aac51565a65c959ee4` - PR type for this branch: `pure-move` -- Runtime behavior changes: no external behavior change expected; server-ready - publication, global init time, scanner start, shutdown wait, and shutdown - ordering still run in the same relative order after runtime services. -- Rust code changes: add `startup_services::run_startup_runtime_lifecycle` and - use it from binary startup. +- Runtime behavior changes: no external behavior change expected; the binary + entrypoint still builds the Tokio runtime, applies preflight, dispatches + non-server commands, and starts server/bootstrap stages in the same order. +- Rust code changes: add `startup_entrypoint::run_process`, move remaining + command dispatch, server bootstrap orchestration, and fatal stderr helpers + out of `main.rs`, and keep `main.rs` as the process entry shim. - CI/script changes: none. -- Docs changes: record `R-018` startup runtime lifecycle progress and - verification. +- Docs changes: record `R-019` startup command/bootstrap entrypoint progress + and update startup timeline ownership. ## Phase 0 Tasks @@ -721,10 +722,26 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block formatting, migration guards, Rust risk scan, branch freshness check, and pre-commit quality gate. +- [x] `R-019` Centralize startup command and bootstrap entrypoint. + - Do: move Tokio runtime result handling, command parsing/dispatch, server + preflight error mapping, startup run orchestration, and pre-observability + fatal stderr formatting behind `startup_entrypoint::run_process`. + - Acceptance: `main.rs` only owns the global allocator declarations and calls + the startup entrypoint; `startup_entrypoint` preserves the existing + command, preflight, listen, storage, runtime-service, ready, and shutdown + order. + - Must preserve: Tokio runtime build fatal `expect`, command parse fatal + stderr context and exit code, info/TLS subcommand behavior, observability + fatal sentinel suppression, server runtime failure log fields, startup stage + ordering, readiness publication, and shutdown ownership. + - Verification: focused startup entrypoint and observability guardrail tests, + binary/lib compile checks, formatting, migration guards, Rust risk scan, + branch freshness check, and pre-commit quality gate. + ## Next PRs -1. `pure-move`: continue collapsing remaining binary startup glue around command - dispatch and server bootstrap without changing fatal stderr behavior. +1. `pure-move`: continue shrinking startup entrypoint orchestration only where a + clean module boundary remains without changing fatal stderr behavior. 2. `ci-gate`: finish `G-006` public re-export and storage trait coverage checks before the remaining cleanup slices. @@ -732,40 +749,43 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block | Expert | Status | Notes | |---|---|---| -| Quality/architecture | passed | Pure-move slice removes ready/scanner/shutdown lifecycle details from binary startup behind the existing startup services boundary. | -| Migration preservation | passed | Server-ready publication, scanner start, shutdown wait, background/protocol/notifier/audit/profiling/HTTP shutdown order, and stopped state are preserved. | -| Testing/verification | passed | Focused startup services tests, compile checks, formatting, migration/layer guards, Rust risk scan, branch freshness check, and full `make pre-commit` passed. | +| Quality/architecture | passed | Pure-move slice removes command dispatch and bootstrap orchestration details from binary startup behind a dedicated startup entrypoint boundary. | +| Migration preservation | passed | Command parse stderr, info/TLS dispatch, observability fatal sentinel, startup order, readiness publication, and shutdown ownership are preserved. | +| Testing/verification | passed | Focused startup entrypoint and observability guardrail tests, compile checks, formatting, migration/layer guards, Rust risk scan, branch freshness check, and full `make pre-commit` passed. | ## Verification Notes -Passed on `e26eea7f3a5ef6c272a322a47f58f5a10cb2342b0`: +Passed on `941b8afee8619c6a038c20aac51565a65c959ee4`: -- `cargo test -p rustfs startup_services --no-fail-fast`: passed. +- `cargo test -p rustfs startup_entrypoint --no-fail-fast`: passed. +- `cargo test -p rustfs-obs startup_ --no-fail-fast`: passed. - `cargo check -p rustfs --lib`: passed. - `cargo check -p rustfs --bin rustfs`: passed. - `cargo fmt --all --check`: passed. - `git diff --check`: passed. - `./scripts/check_architecture_migration_rules.sh`: passed. - `./scripts/check_layer_dependencies.sh`: passed. -- Added-line Rust risk scan for changed Rust files: passed. -- Full-file risk scan for changed Rust files: existing `main.rs` process setup - entries only. -- `make pre-commit`: passed, including nextest `6027` passed / `111` skipped +- Added-line and changed-file Rust risk scan: expected fatal-boundary + `expect`/`eprintln!` only. +- Full-file risk scan for changed Rust files: expected process exit and fatal + stderr entries are isolated in `startup_entrypoint.rs`. +- `make pre-commit`: passed, including nextest `6038` passed / `111` skipped and doctests. - `git rev-list --left-right --count HEAD...origin/main`: returned `1 0` after commit. Notes: -- This slice centralizes startup lifecycle completion without changing startup - ordering, shutdown token ownership, readiness ownership, or shutdown handle - ownership. -- Ready publication and scanner start remain after runtime services; shutdown - wait remains after scanner start. +- This slice centralizes remaining command/bootstrap entrypoint orchestration + without changing startup ordering, fatal stderr behavior, readiness ownership, + shutdown token ownership, or shutdown handle ownership. +- Ready publication, scanner start, and shutdown wait remain owned by + `startup_services`; `startup_entrypoint` only orchestrates the existing + startup sequence and process-level fatal handling. ## Handoff Notes -- R-018 is implemented, locally verified, and current with `origin/main`. +- R-019 is implemented, locally verified, and current with `origin/main`. - Next startup slices can keep using larger pure moves, but must keep startup ordering, fatal/non-fatal boundaries, shutdown ownership, readiness ownership, and fatal stderr behavior explicit in tests and review notes. diff --git a/docs/architecture/startup-timeline.md b/docs/architecture/startup-timeline.md index 240f932b1..cd628bd99 100644 --- a/docs/architecture/startup-timeline.md +++ b/docs/architecture/startup-timeline.md @@ -7,8 +7,9 @@ new startup semantics. ## Scope - Baseline commit: `ae9d25879d72bc8977f08e61062c022e2142483b` -- Entry points covered: `rustfs/src/main.rs::main`, `async_main`, `run`, and - `handle_shutdown` +- Entry points covered: `rustfs/src/main.rs::main`, + `rustfs/src/startup_entrypoint.rs::{run_process, async_main, run}`, and + startup lifecycle helpers - Related migration task: `G-007` - Out of scope for this baseline: embedded startup, admin route-action matrix, and any runtime/lifecycle code movement @@ -17,22 +18,22 @@ new startup semantics. | Step | Source | Current action | Side effects | Fatal boundary | Ready stage | |---|---|---|---|---|---| -| `BOOT-001` | `rustfs/src/main.rs:84` | Apply external-prefix environment compatibility before the Tokio runtime is created. | Copies supported external env aliases into canonical `RUSTFS_*` process env keys and prints warnings or info to stderr. | Non-fatal; failure is logged to stderr and startup continues. | None | -| `BOOT-002` | `rustfs/src/main.rs:89` | Build the Tokio runtime. | Installs runtime configuration and any runtime telemetry guard created by the runtime builder. | Fatal through `expect`; process exits if the runtime cannot be built. | None | -| `BOOT-003` | `rustfs/src/main.rs:139` | Parse CLI command and dispatch non-server commands. | `info` and `tls` commands execute and return without server startup. | Command parse exits process with code 1; TLS command errors propagate. | None | -| `BOOT-004` | `rustfs/src/main.rs:167` | Initialize config snapshot and license state. | Publishes config snapshot for later readers and initializes runtime license state. | License init is non-fallible in this path. | None | -| `BOOT-005` | `rustfs/src/main.rs:173` | Initialize observability and store the global guard. | Initializes tracing/observability, stores the guard globally, and logs license/runtime telemetry status. | Fatal if observability init or guard publication fails. | None | -| `BOOT-006` | `rustfs/src/main.rs:208` | Log startup logo, initialize profiling, trusted proxies, rustls provider, and outbound TLS material. | Starts optional profiling tasks, trusted proxy config, default rustls provider, outbound TLS global state, TLS generation metric, and TLS metrics when enabled. | Profiling/proxy/provider setup is non-fatal; configured TLS material load is fatal on error. | None | -| `RUN-001` | `rustfs/src/main.rs:256` | Enter `run` and create `GlobalReadiness`. | Allocates the readiness tracker shared with HTTP readiness gates. | Non-fatal. | Initial readiness state is not ready | -| `RUN-002` | `rustfs/src/main.rs:261` | Parse and publish the configured region. | Updates ECStore global region when configured. | Fatal if the configured region is invalid. | None | -| `RUN-003` | `rustfs/src/main.rs:268` | Resolve server address and warn on default credentials. | Computes server port/address and emits production credential warning when defaults are used. | Address parse is fatal; default credentials warning is non-fatal. | None | -| `RUN-004` | `rustfs/src/main.rs:286` | Initialize global action credentials. | Publishes root/action credentials used by auth paths. | Fatal if global credentials cannot be initialized. | None | -| `RUN-005` | `rustfs/src/main.rs:298` | Publish server port and address. | Updates global RustFS port and global address. | Non-fatal in this path. | None | -| `RUN-006` | `rustfs/src/main.rs:302` | Build endpoint pools and enforce unsupported filesystem policy. | Derives pool/set/disk layout from configured volumes and validates unsupported filesystem policy. | Fatal on endpoint build or unsupported filesystem policy error. | None | -| `RUN-007` | `rustfs/src/main.rs:308` | Publish endpoints and erasure type. | Updates global endpoints and erasure type. | Non-fatal in this path. | None | -| `RUN-008` | `rustfs/src/main.rs:311` | Initialize local disks, prewarm local disk id map, and initialize lock clients. | Opens local disk state, primes disk id lookup, and creates global lock clients. | Local disk init is fatal; prewarm and lock-client setup are non-fatal in this path. | None | -| `RUN-009` | `rustfs/src/main.rs:350` | Initialize capacity management and service state manager. | Starts capacity management and moves service state to `Starting`. | Non-fatal in this path. | None | -| `RUN-010` | `rustfs/src/main.rs:356` | Start S3 HTTP listener and optional console listener before storage is ready. | Starts HTTP servers with readiness gates; console listener starts only when enabled and configured. | Fatal if a configured listener cannot start. | Requests remain gated until full readiness except probe/admin/console/rpc/tonic/table-catalog exempt paths | +| `BOOT-001` | `rustfs/src/startup_entrypoint.rs` | Apply external-prefix environment compatibility during async startup before command parsing. | Copies supported external env aliases into canonical `RUSTFS_*` process env keys and prints warnings or info to stderr. | Non-fatal; failure is logged to stderr and startup continues. | None | +| `BOOT-002` | `rustfs/src/main.rs` and `rustfs/src/startup_entrypoint.rs` | Call the startup entrypoint and build the Tokio runtime. | Installs runtime configuration and any runtime telemetry guard created by the runtime builder. | Fatal through `expect`; process exits if the runtime cannot be built. | None | +| `BOOT-003` | `rustfs/src/startup_entrypoint.rs` | Parse CLI command and dispatch non-server commands. | `info` and `tls` commands execute and return without server startup. | Command parse exits process with code 1; TLS command errors propagate. | None | +| `BOOT-004` | `rustfs/src/startup_preflight.rs` | Initialize config snapshot and license state. | Publishes config snapshot for later readers and initializes runtime license state. | License init is non-fallible in this path. | None | +| `BOOT-005` | `rustfs/src/startup_preflight.rs` | Initialize observability and store the global guard. | Initializes tracing/observability, stores the guard globally, and logs license/runtime telemetry status. | Fatal if observability init or guard publication fails. | None | +| `BOOT-006` | `rustfs/src/startup_runtime.rs` | Log startup logo, initialize profiling, trusted proxies, rustls provider, and outbound TLS material. | Starts optional profiling tasks, trusted proxy config, default rustls provider, outbound TLS global state, TLS generation metric, and TLS metrics when enabled. | Profiling/proxy/provider setup is non-fatal; configured TLS material load is fatal on error. | None | +| `RUN-001` | `rustfs/src/startup_server.rs` | Enter startup run orchestration and create `GlobalReadiness`. | Allocates the readiness tracker shared with HTTP readiness gates. | Non-fatal. | Initial readiness state is not ready | +| `RUN-002` | `rustfs/src/startup_server.rs` | Parse and publish the configured region. | Updates ECStore global region when configured. | Fatal if the configured region is invalid. | None | +| `RUN-003` | `rustfs/src/startup_server.rs` | Resolve server address and warn on default credentials. | Computes server port/address and emits production credential warning when defaults are used. | Address parse is fatal; default credentials warning is non-fatal. | None | +| `RUN-004` | `rustfs/src/startup_server.rs` | Initialize global action credentials. | Publishes root/action credentials used by auth paths. | Fatal if global credentials cannot be initialized. | None | +| `RUN-005` | `rustfs/src/startup_server.rs` | Publish server port and address. | Updates global RustFS port and global address. | Non-fatal in this path. | None | +| `RUN-006` | `rustfs/src/startup_storage.rs` | Build endpoint pools and enforce unsupported filesystem policy. | Derives pool/set/disk layout from configured volumes and validates unsupported filesystem policy. | Fatal on endpoint build or unsupported filesystem policy error. | None | +| `RUN-007` | `rustfs/src/startup_storage.rs` | Publish endpoints and erasure type. | Updates global endpoints and erasure type. | Non-fatal in this path. | None | +| `RUN-008` | `rustfs/src/startup_storage.rs` | Initialize local disks, prewarm local disk id map, and initialize lock clients. | Opens local disk state, primes disk id lookup, and creates global lock clients. | Local disk init is fatal; prewarm and lock-client setup are non-fatal in this path. | None | +| `RUN-009` | `rustfs/src/startup_server.rs` | Initialize capacity management and service state manager. | Starts capacity management and moves service state to `Starting`. | Non-fatal in this path. | None | +| `RUN-010` | `rustfs/src/startup_server.rs` | Start S3 HTTP listener and optional console listener before storage is ready. | Starts HTTP servers with readiness gates; console listener starts only when enabled and configured. | Fatal if a configured listener cannot start. | Requests remain gated until full readiness except probe/admin/console/rpc/tonic/table-catalog exempt paths | | `RUN-011` | `rustfs/src/startup_storage.rs` | Create cancellation token and initialize `ECStore`. | Creates the runtime cancellation token and storage engine. | Fatal if `ECStore::new` fails. | None | | `RUN-012` | `rustfs/src/startup_storage.rs` | Initialize ECStore config and global config system. | Initializes ECStore config, attempts server-config migration, then retries global config init up to 15 times. | Migration attempt is non-fatal in this path; global config init becomes fatal after retries. | Marks the `GlobalReadiness` `StorageReady` stage after global config init succeeds; later runtime readiness still rechecks storage, IAM, and lock quorum before `FullReady` | | `RUN-013` | `rustfs/src/startup_storage.rs` and `rustfs/src/startup_services.rs` | Start replication and KMS systems. | Starts background replication pool, then initializes KMS from startup services. | Replication init is non-fatal in this path; KMS init is fatal on error. | `StorageReady` stage is already marked; dynamic runtime storage readiness is still checked before `FullReady` | diff --git a/rustfs/src/lib.rs b/rustfs/src/lib.rs index e9af2f857..7593e2261 100644 --- a/rustfs/src/lib.rs +++ b/rustfs/src/lib.rs @@ -67,6 +67,7 @@ pub mod profiling; #[cfg(any(feature = "ftps", feature = "webdav", feature = "sftp"))] pub mod protocols; pub mod server; +pub mod startup_entrypoint; pub mod startup_fs_guard; pub mod startup_iam; pub mod startup_preflight; diff --git a/rustfs/src/main.rs b/rustfs/src/main.rs index e603775ca..8a91c3bcc 100644 --- a/rustfs/src/main.rs +++ b/rustfs/src/main.rs @@ -12,18 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Ensure the correct path for parse_license is imported -use rustfs::startup_preflight::{StartupServerPreflightError, bootstrap_external_prefix_compat, init_startup_server_preflight}; -use rustfs::startup_server::{StartupHttpServers, StartupListenContext, init_startup_http_servers, init_startup_listen_context}; -use rustfs::startup_services::{StartupRuntimeLifecycle, init_startup_runtime_services, run_startup_runtime_lifecycle}; -use rustfs::startup_storage::{StartupStorageRuntime, init_startup_storage_foundation, init_startup_storage_runtime}; -use std::io::{Error, Result}; -use tracing::{error, instrument}; - -const LOG_COMPONENT_MAIN: &str = "main"; -const LOG_SUBSYSTEM_STARTUP: &str = "startup"; -const EVENT_SERVER_RUNTIME_FAILED: &str = "server_runtime_failed"; -const OBSERVABILITY_INIT_FATAL_ALREADY_REPORTED: &str = "observability initialization failure already reported"; #[cfg(all(target_os = "linux", target_env = "gnu", target_arch = "x86_64"))] #[global_allocator] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; @@ -32,129 +20,6 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; -fn format_fatal_stderr_message(context: &str, error: impl std::fmt::Display) -> String { - format!("[FATAL] {context}: {error}") -} - -fn emit_fatal_stderr(context: &str, error: impl std::fmt::Display) { - eprintln!("{}", format_fatal_stderr_message(context, error)); -} - fn main() { - // Build Tokio runtime with optional dial9 telemetry support - let runtime = rustfs::server::build_tokio_runtime().expect("Failed to build Tokio runtime"); - let result = runtime.block_on(async_main()); - if let Err(ref e) = result { - if e.to_string() != OBSERVABILITY_INIT_FATAL_ALREADY_REPORTED { - // Tracing may not be initialized when startup fails this early. - emit_fatal_stderr("Server runtime failed", e); - } - std::process::exit(1); - } -} - -async fn async_main() -> Result<()> { - let env_compat_report = bootstrap_external_prefix_compat()?; - - // Parse command line arguments - let args: Vec = std::env::args().collect(); - let command_result = match rustfs::config::Opt::parse_command(args) { - Ok(result) => result, - Err(e) => { - emit_fatal_stderr("Command parse failed", e); - std::process::exit(1); - } - }; - - // Execute subcommand, or prepare config for `server` subcommand - let config = match command_result { - rustfs::config::CommandResult::Info(opts) => { - rustfs::config::execute_info(&opts); - return Ok(()); - } - rustfs::config::CommandResult::Tls(opts) => return rustfs::tls::execute_tls(&opts), - rustfs::config::CommandResult::Server(config) => config, - }; - - match init_startup_server_preflight(&config, &env_compat_report).await { - Ok(()) => {} - Err(StartupServerPreflightError::ObservabilityInit(err)) => { - // Structured logging is unavailable until observability initializes. - emit_fatal_stderr("Observability initialization failed", err); - return Err(Error::other(OBSERVABILITY_INIT_FATAL_ALREADY_REPORTED)); - } - Err(StartupServerPreflightError::Other(err)) => return Err(err), - } - - // Run parameters - match run(*config).await { - Ok(_) => Ok(()), - Err(e) => { - error!( - target: "rustfs::main", - event = EVENT_SERVER_RUNTIME_FAILED, - component = LOG_COMPONENT_MAIN, - subsystem = LOG_SUBSYSTEM_STARTUP, - error = %e, - "Server runtime failed" - ); - Err(e) - } - } -} - -#[instrument(skip(config))] -async fn run(config: rustfs::config::Config) -> Result<()> { - let StartupListenContext { - readiness, - server_addr, - server_address, - } = init_startup_listen_context(&config).await?; - - let endpoint_pools = init_startup_storage_foundation(&server_address, &config.volumes).await?; - let StartupHttpServers { - state_manager, - s3_shutdown_tx, - console_shutdown_tx, - } = init_startup_http_servers(&config, readiness.clone()).await?; - - let StartupStorageRuntime { - store, - shutdown_token: ctx, - } = init_startup_storage_runtime(server_addr, &endpoint_pools, readiness.clone()).await?; - - let service_runtime = init_startup_runtime_services( - &config, - endpoint_pools, - store.clone(), - ctx.clone(), - readiness.clone(), - state_manager.clone(), - ) - .await?; - - run_startup_runtime_lifecycle(StartupRuntimeLifecycle { - server_address, - state_manager, - s3_shutdown_tx, - console_shutdown_tx, - service_runtime, - store, - shutdown_token: ctx, - readiness, - }) - .await -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn fatal_stderr_message_uses_consistent_prefix_and_context() { - assert_eq!( - format_fatal_stderr_message("Observability initialization failed", "collector unavailable"), - "[FATAL] Observability initialization failed: collector unavailable" - ); - } + rustfs::startup_entrypoint::run_process(); } diff --git a/rustfs/src/startup_entrypoint.rs b/rustfs/src/startup_entrypoint.rs new file mode 100644 index 000000000..f9faff586 --- /dev/null +++ b/rustfs/src/startup_entrypoint.rs @@ -0,0 +1,155 @@ +// 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 crate::{ + config::{CommandResult, Config, Opt}, + startup_preflight::{StartupServerPreflightError, bootstrap_external_prefix_compat, init_startup_server_preflight}, + startup_server::{StartupHttpServers, StartupListenContext, init_startup_http_servers, init_startup_listen_context}, + startup_services::{StartupRuntimeLifecycle, init_startup_runtime_services, run_startup_runtime_lifecycle}, + startup_storage::{StartupStorageRuntime, init_startup_storage_foundation, init_startup_storage_runtime}, +}; +use std::io::{Error, Result}; +use tracing::{error, instrument}; + +const LOG_COMPONENT_MAIN: &str = "main"; +const LOG_SUBSYSTEM_STARTUP: &str = "startup"; +const EVENT_SERVER_RUNTIME_FAILED: &str = "server_runtime_failed"; +const OBSERVABILITY_INIT_FATAL_ALREADY_REPORTED: &str = "observability initialization failure already reported"; + +pub fn run_process() { + // Building the process runtime is a startup fatal boundary. + let runtime = crate::server::build_tokio_runtime().expect("Failed to build Tokio runtime"); + let result = runtime.block_on(async_main()); + if let Err(ref e) = result { + if e.to_string() != OBSERVABILITY_INIT_FATAL_ALREADY_REPORTED { + // Tracing may not be initialized when startup fails this early. + emit_fatal_stderr("Server runtime failed", e); + } + std::process::exit(1); + } +} + +fn format_fatal_stderr_message(context: &str, error: impl std::fmt::Display) -> String { + format!("[FATAL] {context}: {error}") +} + +fn emit_fatal_stderr(context: &str, error: impl std::fmt::Display) { + // Pre-observability startup failures cannot rely on tracing. + eprintln!("{}", format_fatal_stderr_message(context, error)); +} + +async fn async_main() -> Result<()> { + let env_compat_report = bootstrap_external_prefix_compat()?; + + // Parse command line arguments + let args: Vec = std::env::args().collect(); + let command_result = match Opt::parse_command(args) { + Ok(result) => result, + Err(e) => { + emit_fatal_stderr("Command parse failed", e); + std::process::exit(1); + } + }; + + // Execute subcommand, or prepare config for `server` subcommand + let config = match command_result { + CommandResult::Info(opts) => { + crate::config::execute_info(&opts); + return Ok(()); + } + CommandResult::Tls(opts) => return crate::tls::execute_tls(&opts), + CommandResult::Server(config) => config, + }; + + match init_startup_server_preflight(&config, &env_compat_report).await { + Ok(()) => {} + Err(StartupServerPreflightError::ObservabilityInit(err)) => { + // Structured logging is unavailable until observability initializes. + emit_fatal_stderr("Observability initialization failed", err); + return Err(Error::other(OBSERVABILITY_INIT_FATAL_ALREADY_REPORTED)); + } + Err(StartupServerPreflightError::Other(err)) => return Err(err), + } + + match run(*config).await { + Ok(_) => Ok(()), + Err(e) => { + error!( + target: "rustfs::main", + event = EVENT_SERVER_RUNTIME_FAILED, + component = LOG_COMPONENT_MAIN, + subsystem = LOG_SUBSYSTEM_STARTUP, + error = %e, + "Server runtime failed" + ); + Err(e) + } + } +} + +#[instrument(skip(config))] +async fn run(config: Config) -> Result<()> { + let StartupListenContext { + readiness, + server_addr, + server_address, + } = init_startup_listen_context(&config).await?; + + let endpoint_pools = init_startup_storage_foundation(&server_address, &config.volumes).await?; + let StartupHttpServers { + state_manager, + s3_shutdown_tx, + console_shutdown_tx, + } = init_startup_http_servers(&config, readiness.clone()).await?; + + let StartupStorageRuntime { + store, + shutdown_token: ctx, + } = init_startup_storage_runtime(server_addr, &endpoint_pools, readiness.clone()).await?; + + let service_runtime = init_startup_runtime_services( + &config, + endpoint_pools, + store.clone(), + ctx.clone(), + readiness.clone(), + state_manager.clone(), + ) + .await?; + + run_startup_runtime_lifecycle(StartupRuntimeLifecycle { + server_address, + state_manager, + s3_shutdown_tx, + console_shutdown_tx, + service_runtime, + store, + shutdown_token: ctx, + readiness, + }) + .await +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fatal_stderr_message_uses_consistent_prefix_and_context() { + assert_eq!( + format_fatal_stderr_message("Observability initialization failed", "collector unavailable"), + "[FATAL] Observability initialization failed: collector unavailable" + ); + } +}