This is an automated email from the ASF dual-hosted git repository.

spetz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new f67f03873 fix(server-ng): fold io_uring remediation into 
runtime-creation errors (#3775)
f67f03873 is described below

commit f67f038731f5c074ee14ed23182c0637d2f6f156
Author: Grzegorz Koszyk <[email protected]>
AuthorDate: Wed Jul 29 16:22:51 2026 +0200

    fix(server-ng): fold io_uring remediation into runtime-creation errors 
(#3775)
---
 Cargo.toml                            |   5 +-
 core/server-ng/src/bootstrap.rs       |  11 +++-
 core/server-ng/src/main.rs            |  13 +---
 core/server-ng/src/server_error.rs    |   6 +-
 core/server/src/main.rs               |  40 ++++---------
 core/server_common/src/diagnostics.rs | 108 +++++++++++++++++++++++++++++++++-
 6 files changed, 137 insertions(+), 46 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 153461028..0147c62ed 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -85,7 +85,10 @@ aligned-vec = "0.6.4"
 anyhow = "1.0.104"
 apache-avro = "0.21.0"
 apple-native-keyring-store = { version = "1.0.1", features = ["keychain"] }
-argon2 = "0.5.3"
+# "std" is load-bearing: it cascades to rand_core/getrandom, which the
+# crypto module's `OsRng` import needs even when no sibling crate in the
+# build graph happens to enable it via feature unification.
+argon2 = { version = "0.5.3", features = ["std"] }
 # Pinned to 57 because iceberg 0.9.1 still requires arrow/parquet 57.
 arrow = "57.3.1"
 arrow-array = "57.3.1"
diff --git a/core/server-ng/src/bootstrap.rs b/core/server-ng/src/bootstrap.rs
index aed10ffb0..db31f2903 100644
--- a/core/server-ng/src/bootstrap.rs
+++ b/core/server-ng/src/bootstrap.rs
@@ -797,8 +797,15 @@ fn run_shard_thread(
         .bind_memory()
         .map_err(|source| ServerNgError::MemoryAffinityFailed { shard_id, 
source })?;
 
-    let runtime = create_shard_executor()
-        .map_err(|source| ServerNgError::ShardRuntimeCreateFailed { shard_id, 
source })?;
+    // `enrich_runtime_create_error` folds the io_uring remediation (raise
+    // `ulimit -l`, unblock seccomp, kernel-flag floor) into the error, so the
+    // guidance survives into the shard-join failure report instead of only
+    // stderr. Multi-shard boxes exhaust RLIMIT_MEMLOCK on per-shard rings
+    // before the bootstrap runtime does, so this path needs it most.
+    let runtime = create_shard_executor().map_err(|source| {
+        let source = 
server_common::diagnostics::enrich_runtime_create_error(source);
+        ServerNgError::ShardRuntimeCreateFailed { shard_id, source }
+    })?;
 
     let result = runtime.block_on(async move {
         // `shard_main`'s future grows past clippy's `large_futures` cap
diff --git a/core/server-ng/src/main.rs b/core/server-ng/src/main.rs
index a3e11fd64..404a4496f 100644
--- a/core/server-ng/src/main.rs
+++ b/core/server-ng/src/main.rs
@@ -33,18 +33,7 @@ fn main() -> Result<(), ServerNgError> {
     let bootstrap_runtime = match server_common::create_shard_executor() {
         Ok(rt) => rt,
         Err(e) => {
-            match e.kind() {
-                std::io::ErrorKind::InvalidInput => {
-                    
server_common::diagnostics::print_invalid_io_uring_args_info();
-                }
-                std::io::ErrorKind::OutOfMemory => {
-                    
server_common::diagnostics::print_locked_memory_limit_info();
-                }
-                std::io::ErrorKind::PermissionDenied => {
-                    
server_common::diagnostics::print_io_uring_permission_info();
-                }
-                _ => {}
-            }
+            let e = server_common::diagnostics::enrich_runtime_create_error(e);
             panic!("Cannot create server-ng bootstrap executor: {e}");
         }
     };
diff --git a/core/server-ng/src/server_error.rs 
b/core/server-ng/src/server_error.rs
index b19f26778..61cd1adf4 100644
--- a/core/server-ng/src/server_error.rs
+++ b/core/server-ng/src/server_error.rs
@@ -48,7 +48,11 @@ pub enum ServerNgError {
         #[source]
         source: std::io::Error,
     },
-    #[error("failed to create io_uring runtime for shard {shard_id}")]
+    // `{source}` is deliberately part of the Display text: the shard-join
+    // failure report and `%error` log fields print Display only, and the
+    // source carries the io_uring remediation folded in by
+    // `server_common::diagnostics::enrich_runtime_create_error`.
+    #[error("failed to create io_uring runtime for shard {shard_id}: 
{source}")]
     ShardRuntimeCreateFailed {
         shard_id: u16,
         #[source]
diff --git a/core/server/src/main.rs b/core/server/src/main.rs
index b5c0110f7..1f0ac18d9 100644
--- a/core/server/src/main.rs
+++ b/core/server/src/main.rs
@@ -29,11 +29,7 @@ use server::bootstrap::{
     create_directories, create_shard_connections, create_shard_executor, 
load_config,
     load_metadata, resolve_persister, update_system_info,
 };
-use server::diagnostics::{
-    ASYNCIFY_POOL_DISABLED_PANIC_MSG, print_incomplete_io_uring_ops_info,
-    print_invalid_io_uring_args_info, print_io_uring_permission_info,
-    print_locked_memory_limit_info,
-};
+use server::diagnostics::{ASYNCIFY_POOL_DISABLED_PANIC_MSG, 
print_incomplete_io_uring_ops_info};
 use server::io::fs_utils;
 use server::log::logger::Logging;
 use server::metadata::{Metadata, create_metadata_handles};
@@ -65,10 +61,10 @@ const SHARDS_TABLE_CAPACITY: usize = 16384;
 
 static SHUTDOWN_START_TIME: AtomicU64 = AtomicU64::new(0);
 static SHUTDOWN_INITIATED: AtomicBool = AtomicBool::new(false);
-static SHARD_EXECUTOR_DIAGNOSTIC: std::sync::Once = std::sync::Once::new();
-// Separate latch from SHARD_EXECUTOR_DIAGNOSTIC: a shard that fails ring setup
-// (e.g. partial ENOMEM under a tight RLIMIT_MEMLOCK) must not consume the 
latch
-// and suppress the unsupported-opcode diagnostic from a sibling shard that did
+// Separate latch from the ring-setup one inside
+// `enrich_runtime_create_error`: a shard that fails ring setup (e.g. partial
+// ENOMEM under a tight RLIMIT_MEMLOCK) must not consume the latch and
+// suppress the unsupported-opcode diagnostic from a sibling shard that did
 // start. Setup vs runtime io_uring failures can co-occur across shards.
 static SHARD_RUNTIME_DIAGNOSTIC: std::sync::Once = std::sync::Once::new();
 
@@ -123,11 +119,7 @@ fn main() -> Result<(), ServerError> {
     let rt = match compio::runtime::Runtime::new() {
         Ok(rt) => rt,
         Err(e) => {
-            match e.kind() {
-                std::io::ErrorKind::OutOfMemory => 
print_locked_memory_limit_info(),
-                std::io::ErrorKind::PermissionDenied => 
print_io_uring_permission_info(),
-                _ => {}
-            }
+            let e = server_common::diagnostics::enrich_runtime_create_error(e);
             panic!("Cannot create runtime: {e}");
         }
     };
@@ -417,21 +409,11 @@ fn main() -> Result<(), ServerError> {
                         let rt = match create_shard_executor() {
                             Ok(rt) => rt,
                             Err(e) => {
-                                match e.kind() {
-                                    std::io::ErrorKind::InvalidInput => {
-                                        SHARD_EXECUTOR_DIAGNOSTIC
-                                            
.call_once(print_invalid_io_uring_args_info);
-                                    }
-                                    std::io::ErrorKind::OutOfMemory => {
-                                        SHARD_EXECUTOR_DIAGNOSTIC
-                                            
.call_once(print_locked_memory_limit_info);
-                                    }
-                                    std::io::ErrorKind::PermissionDenied => {
-                                        SHARD_EXECUTOR_DIAGNOSTIC
-                                            
.call_once(print_io_uring_permission_info);
-                                    }
-                                    _ => {}
-                                }
+                                // Prints the verbose remediation once across
+                                // all shard threads; the panic message itself
+                                // carries the one-line fix.
+                                let e =
+                                    
server_common::diagnostics::enrich_runtime_create_error(e);
                                 panic!("Cannot create shard-{id} executor: 
{e}");
                             }
                         };
diff --git a/core/server_common/src/diagnostics.rs 
b/core/server_common/src/diagnostics.rs
index 10059423f..2b745d7e4 100644
--- a/core/server_common/src/diagnostics.rs
+++ b/core/server_common/src/diagnostics.rs
@@ -31,6 +31,79 @@ pub const ASYNCIFY_POOL_DISABLED_PANIC_MSG: &str =
 #[cfg(target_os = "linux")]
 const DISCORD_SUPPORT_URL: &str = "https://discord.gg/apache-iggy";;
 
+/// Classify a failed shard-executor creation and fold the matching
+/// remediation into the error itself.
+///
+/// The verbose remediation block (current limits, per-environment fix
+/// steps) is printed to stderr once per process; the returned error
+/// carries a one-line summary of the cause and the primary fix, so every
+/// propagated copy - the shard-join failure list, a panic message, a log
+/// line hours later in a collector - documents the remediation instead
+/// of only the stderr captured at the moment of failure.
+///
+/// Errors of a kind this module has no diagnosis for pass through
+/// unchanged.
+#[cfg(target_os = "linux")]
+pub fn enrich_runtime_create_error(error: std::io::Error) -> std::io::Error {
+    static RUNTIME_CREATE_DIAGNOSTIC: std::sync::Once = std::sync::Once::new();
+
+    let kind = error.kind();
+    let hint = match kind {
+        std::io::ErrorKind::OutOfMemory => {
+            
RUNTIME_CREATE_DIAGNOSTIC.call_once(print_locked_memory_limit_info);
+            locked_memory_limit_hint()
+        }
+        std::io::ErrorKind::PermissionDenied => {
+            
RUNTIME_CREATE_DIAGNOSTIC.call_once(print_io_uring_permission_info);
+            "io_uring syscalls are blocked, typically by a container seccomp \
+             profile: allow io_uring_setup/io_uring_enter/io_uring_register, \
+             or run with `--security-opt seccomp=unconfined` (Docker) / \
+             `seccompProfile: {type: Unconfined}` (Kubernetes)"
+                .to_owned()
+        }
+        std::io::ErrorKind::InvalidInput => {
+            
RUNTIME_CREATE_DIAGNOSTIC.call_once(print_invalid_io_uring_args_info);
+            format!(
+                "the kernel rejected io_uring setup flags shard executors 
require \
+                 (IORING_SETUP_COOP_TASKRUN + IORING_SETUP_TASKRUN_FLAG need 
Linux \
+                 >= {MIN_KERNEL_MAJOR}.{MIN_KERNEL_MINOR} with full io_uring 
support; \
+                 WSL2 kernels are often incomplete)"
+            )
+        }
+        _ => return error,
+    };
+    std::io::Error::new(kind, format!("{error}: {hint}"))
+}
+
+#[cfg(not(target_os = "linux"))]
+pub fn enrich_runtime_create_error(error: std::io::Error) -> std::io::Error {
+    error
+}
+
+/// One-line remediation for an io_uring ring allocation denied by
+/// `RLIMIT_MEMLOCK`, with the live limits baked in so a log line is
+/// self-sufficient evidence of the misconfiguration.
+#[cfg(target_os = "linux")]
+fn locked_memory_limit_hint() -> String {
+    use nix::sys::resource::{Resource, getrlimit};
+
+    let limits = getrlimit(Resource::RLIMIT_MEMLOCK).map_or_else(
+        |_| "RLIMIT_MEMLOCK could not be read".to_owned(),
+        |(soft, hard)| {
+            format!(
+                "RLIMIT_MEMLOCK soft={}, hard={}",
+                format_limit(soft),
+                format_limit(hard)
+            )
+        },
+    );
+    format!(
+        "io_uring was denied locked memory for its rings ({limits}): raise the 
\
+         limit with `ulimit -l unlimited` (shell), `LimitMEMLOCK=infinity` \
+         (systemd), or `--ulimit memlock=-1:-1` (Docker)"
+    )
+}
+
 #[cfg(target_os = "linux")]
 fn print_discord_link() {
     eprintln!("  Need help? Join our Discord: {DISCORD_SUPPORT_URL}");
@@ -359,7 +432,40 @@ pub const fn print_incomplete_io_uring_ops_info() {}
 
 #[cfg(all(test, target_os = "linux"))]
 mod tests {
-    use super::{format_limit, parse_kernel_version};
+    use super::{enrich_runtime_create_error, format_limit, 
parse_kernel_version};
+
+    #[test]
+    fn enrich_folds_memlock_remediation_into_the_error() {
+        let raw = std::io::Error::new(std::io::ErrorKind::OutOfMemory, 
"io_uring setup: ENOMEM");
+        let enriched = enrich_runtime_create_error(raw);
+        let message = enriched.to_string();
+        assert_eq!(enriched.kind(), std::io::ErrorKind::OutOfMemory);
+        assert!(message.contains("io_uring setup: ENOMEM"), "{message}");
+        assert!(message.contains("ulimit -l unlimited"), "{message}");
+        assert!(message.contains("RLIMIT_MEMLOCK"), "{message}");
+    }
+
+    #[test]
+    fn enrich_folds_seccomp_remediation_into_the_error() {
+        let raw = std::io::Error::new(std::io::ErrorKind::PermissionDenied, 
"EPERM");
+        let message = enrich_runtime_create_error(raw).to_string();
+        assert!(message.contains("seccomp"), "{message}");
+    }
+
+    #[test]
+    fn enrich_folds_kernel_flag_remediation_into_the_error() {
+        let raw = std::io::Error::new(std::io::ErrorKind::InvalidInput, 
"EINVAL");
+        let message = enrich_runtime_create_error(raw).to_string();
+        assert!(message.contains("IORING_SETUP_COOP_TASKRUN"), "{message}");
+    }
+
+    #[test]
+    fn enrich_passes_undiagnosed_kinds_through_unchanged() {
+        let raw = std::io::Error::new(std::io::ErrorKind::Interrupted, 
"EINTR");
+        let enriched = enrich_runtime_create_error(raw);
+        assert_eq!(enriched.kind(), std::io::ErrorKind::Interrupted);
+        assert_eq!(enriched.to_string(), "EINTR");
+    }
 
     #[test]
     fn parse_kernel_version_standard() {

Reply via email to