Introduction
Rayon's parallel iterators distribute work across multiple threads. When one element's processing panics, the panic propagates to the calling thread and aborts the entire parallel iteration. All in-progress work on other threads is cancelled, and partial results are lost. This can be particularly problematic in data processing pipelines where some elements may be malformed but the majority should still be processed.
Symptoms
thread panicked at 'index out of bounds'in apar_iter()call- Entire parallel iteration aborts due to a single element's panic
- Partial results from other threads are discarded
- Panic message does not identify which element caused the failure
catch_unwinddoes not work insidepar_iterclosures directly
Example error:
``
thread 'thread 3' panicked at 'index out of bounds: the len is 5 but the index is 10',
src/processor.rs:42:15
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace
Common Causes
- Data inconsistency: some elements have invalid state that triggers panic
- Off-by-one errors that only manifest on certain elements under parallel execution
- External service calls that panic on specific inputs
- Division by zero or arithmetic overflow on specific data values
- Assertion failures that trigger only for edge cases
Step-by-Step Fix
- 1.**Use
filter_mapto handle errors instead of panicking**: - 2.```rust
- 3.use rayon::prelude::*;
// Before: panics on bad elements let results: Vec<_> = items.par_iter() .map(|item| process_item(item).unwrap()) .collect();
// After: collect errors alongside results let (results, errors): (Vec<_>, Vec<_>) = items.par_iter() .map(|item| process_item(item)) .partition_map(|result| match result { Ok(value) => rayon::Either::Left(value), Err(e) => rayon::Either::Right(e), });
eprintln!("Processed {} items, {} errors", results.len(), errors.len()); ```
- 1.**Use
panic::catch_unwindinside parallel iteration**: - 2.```rust
- 3.use std::panic;
- 4.use rayon::prelude::*;
let results: Vec<_> = items.par_iter() .filter_map(|item| { let item = item.clone(); panic::catch_unwind(panic::AssertUnwindSafe(|| { process_item(&item) })).ok() // Convert panic to None }) .collect(); ```
- 1.Identify the problematic element:
- 2.```rust
- 3.let results: Vec<_> = items.par_iter()
- 4..enumerate()
- 5..filter_map(|(i, item)| {
- 6.let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
- 7.process_item(item)
- 8.}));
match result { Ok(value) => Some(Ok(value)), Err(panic_info) => { eprintln!("Panic at index {}: {:?}", i, panic_info); Some(Err((i, panic_info))) } } }) .collect(); ```
- 1.**Use
par_bridgefor fallible iterators**: - 2.```rust
- 3.use rayon::prelude::*;
- 4.use std::sync::Mutex;
// For operations that can fail, collect errors let errors = Mutex::new(Vec::new()); let results: Vec<_> = items.par_iter() .filter_map(|item| { match std::panic::catch_unwind(panic::AssertUnwindSafe(|| { process_item(item) })) { Ok(Ok(value)) => Some(value), Ok(Err(e)) => { errors.lock().unwrap().push(e); None } Err(panic) => { errors.lock().unwrap().push(format!("Panic: {:?}", panic)); None } } }) .collect(); ```
Prevention
- Never use
.unwrap()or.expect()insidepar_iter()closures - Use
Resulttypes andfilter_mapto handle errors gracefully - Use
catch_unwindfor external code that may panic - Validate data before entering parallel iteration
- Add logging that identifies which element is being processed
- Test parallel code with edge cases using
proptestorquickcheck