fix(embedding): drop ort-load-dynamic and make LazyFastembed panic-safe (#50)#51
Merged
Merged
Conversation
…fe (#50) - Stop enabling ort-load-dynamic on the Linux/Windows fastembed target dep. It forwards to ort-sys/disable-linking, which skips the static-archive download step, making ort-download-binaries a silent no-op and leaving the binary expecting libonnxruntime.so at runtime -> silent deadlock on first .embed(). Default build now statically links ONNX Runtime. - Wrap FastembedProvider::new in catch_unwind so a panicking ONNX init transitions ModelState::Failed and notifies all Condvar waiters, instead of wedging concurrent callers forever.
…fety claim - Re-add ort-load-dynamic as opt-in embedding-fastembed-dynamic-linking feature; static linking alone doesn't satisfy glibc <2.38 Linux hosts (the original reason ort-load-dynamic existed, per #50) - CHANGELOG: scope the catch_unwind panic-safety guarantee to unwinding builds — this crate's own panic = "abort" release profile can't catch panics, so a panicking init still aborts in release builds - ModelState::Failed now carries a panicked flag (is_panic()) so callers can distinguish a panic-origin failure from an ordinary load error - Add LazyFastembedProvider::reset() so a Failed provider can retry instead of being permanently stuck
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #50 —
embedding-fastembedon Linux/Windows force-enabledort-load-dynamic, which silently reproduced as an infinitefutexdeadlock instead of a clear error.Two coupled defects, both fixed:
1. Cargo feature interaction (the trigger)
ort-load-dynamicforwards toort-sys/disable-linking, which makesort-sys's build script early-return and skip the static-archive download step entirely. Soort-download-binaries-rustls-tlswas a silent no-op, and the resulting binary expectedlibonnxruntime.soto be supplied externally at runtime — which llm-kernel never ships.Fix: Drop
ort-load-dynamicfrom the Linux/Windowsfastembedtarget dep. The default build now statically links ONNX Runtime and produces self-contained binaries (verified:libloadingdrops out ofCargo.lock,cargo:rustc-link-lib=static=onnxruntimeis emitted).2.
LazyFastembedProviderpanic safety (the hang)A panic during
FastembedProvider::new()(e.g. ort init panicking on a missing dylib — exactly whatload-dynamicproduced) unwound across theMutex/Condvarboundary without transitioningModelStatetoFailedor notifying, wedging any peer thread parked in theLoadingbranch forever. Confirmed in production viastrace//proc/PID/wchan(futex_wait_queue, 0% CPU, no syscalls).Fix: Wrap the load in
std::panic::catch_unwind(AssertUnwindSafe(..)); convert a panic intoModelState::Failed("model init panicked: ..")+cvar.notify_all(). Guards against any future ort/fastembed init failure mode, not just the dynamic-linking one.Why no
embedding-fastembed-dynamicopt-in feature?Investigated and rejected. Cargo feature unification merges
fastembedfeatures into a union across llm-kernel features — so anyembedding-fastembed-dynamicfeature (which must implyembedding-fastembed) would re-enable bothort-download-binariesANDort-load-dynamictogether whenever both were selected, silently reproducing #50 with no compile-time error. Verified empirically viacargo metadata+cargo check -vvshowingCARGO_FEATURE_DISABLE_LINKING=1+CARGO_FEATURE_DOWNLOAD_BINARIES=1set simultaneously.Cargo's mutually-exclusive-features mechanism (
implied-by) is unstable and can't guard this. Deployments that truly need dynamic loading should depend onfastembeddirectly in their own crate and enableort-load-dynamicthere, outside llm-kernel's feature graph.Audit
catch_unwindplacement, all three outcomes (Ok(Ok)/Ok(Err)/Err(panic)) transition state +notify_allexactly once,Innermutex never held across the load (Arc::clone+drop(guard)),AssertUnwindSafesoundness verified (value-captured inputs, no torn state leaks), TOCTOU-free waiter wakeup.Tests added
panic_during_load_transitions_to_failed— a panicking loader →state() == Failed("model init panicked: ..").failure_during_load_transitions_to_failed— a failing loader →Failed.concurrent_waiter_released_when_loader_panics— AC4: a waiter parked inLoadingis released withErr(not hung) when the owning loader panics; verified with a real spawned thread + barrier, noload_timeout_secswait.Tests use an injected loader (
new_with_loader) so no real ONNX weights are required.Behavior change
Consumers that relied on the (broken) dynamic-linking default must now statically link (the new default) — which is what they actually wanted. The dynamic path is documented in CHANGELOG for the rare deployment that needs it.
Verification