From 2dba589916ba7bb9f8d8e9cbde1e4f29876dbc4c Mon Sep 17 00:00:00 2001 From: Chris Petersen Date: Wed, 25 Mar 2026 19:41:34 -0700 Subject: [PATCH] Fix UMAP crashes: upgrade annembed and add panic safety in Rust wrapper - Update annembed dependency to clusterkit-0.2.6 tag which fixes: - LAPACK SGESDD crash from incorrect matrix layout declaration - ndarray index-out-of-bounds in diffusion map embedding - Dimension mismatch between initial embedding and optimization - Empty neighbor array panics - Wrap embedder.embed() in catch_unwind so Rust panics become Ruby exceptions instead of process crashes Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 2 +- ext/clusterkit/Cargo.toml | 2 +- ext/clusterkit/src/embedder.rs | 25 ++++++++++++++++++++++--- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c88f4fb..a07cd1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ dependencies = [ [[package]] name = "annembed" version = "0.1.5" -source = "git+https://github.com/scientist-labs/annembed?tag=clusterkit-0.1.1#7a5803405087c10c82185f113e3befb953cc9bab" +source = "git+https://github.com/scientist-labs/annembed?tag=clusterkit-0.2.6#8f8c451367b3a20817cc9dd9803c56aa0c2b9859" dependencies = [ "anyhow", "bincode 2.0.1", diff --git a/ext/clusterkit/Cargo.toml b/ext/clusterkit/Cargo.toml index f8e5e51..4600724 100644 --- a/ext/clusterkit/Cargo.toml +++ b/ext/clusterkit/Cargo.toml @@ -8,7 +8,7 @@ crate-type = ["cdylib"] [dependencies] magnus = { version = "0.8", features = ["embed"] } -annembed = { git = "https://github.com/scientist-labs/annembed", tag = "clusterkit-0.1.1" } +annembed = { git = "https://github.com/scientist-labs/annembed", tag = "clusterkit-0.2.6" } hnsw_rs = { git = "https://github.com/scientist-labs/hnswlib-rs", tag = "clusterkit-0.1.0" } hdbscan = "0.11" ndarray = "0.16" diff --git a/ext/clusterkit/src/embedder.rs b/ext/clusterkit/src/embedder.rs index 06e8039..67aa1f1 100644 --- a/ext/clusterkit/src/embedder.rs +++ b/ext/clusterkit/src/embedder.rs @@ -178,9 +178,28 @@ impl RustUMAP { // Create embedder and perform embedding let mut embedder = Embedder::new(&kgraph, embed_params); - let embed_result = embedder.embed() - .map_err(|e| Error::new(ruby.exception_runtime_error(), - format!("Embedding failed: {}", e)))?; + let embed_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + embedder.embed() + })); + + let embed_result = match embed_result { + Ok(Ok(result)) => result, + Ok(Err(e)) => { + return Err(Error::new(ruby.exception_runtime_error(), + format!("Embedding failed: {}", e))); + } + Err(panic_info) => { + let msg = if let Some(s) = panic_info.downcast_ref::() { + s.clone() + } else if let Some(s) = panic_info.downcast_ref::<&str>() { + s.to_string() + } else { + "unknown panic".to_string() + }; + return Err(Error::new(ruby.exception_runtime_error(), + format!("Embedding panicked: {}", msg))); + } + }; if embed_result == 0 { return Err(Error::new(ruby.exception_runtime_error(), "No points were embedded"));