Skip to content

Retryable IO errors misclassified when wrapped in DataFusionError::Shared - #2119

Merged
andygrove merged 1 commit into
apache:mainfrom
goingforstudying-ctrl:fix/shared-io-error-retryable
Jul 21, 2026
Merged

Retryable IO errors misclassified when wrapped in DataFusionError::Shared#2119
andygrove merged 1 commit into
apache:mainfrom
goingforstudying-ctrl:fix/shared-io-error-retryable

Conversation

@goingforstudying-ctrl

Copy link
Copy Markdown
Contributor

Hit this while running queries with joins against flaky object storage: a transient IO error on the build side of a hash join kills the whole job instead of getting retried.

Turned out the retry classifier in ballista/core/src/error.rs does a shallow matches!(*e, DataFusionError::IoError(_)) on the outermost variant. But errors coming off a join's shared build side get wrapped in DataFusionError::Shared (an Arc for sharing across consumers), so the IoError inside is never seen and the task falls through to the catch-all retryable: false arm. AQE off works fine because the error arrives unwrapped, which is how I narrowed it down to the wrapping.

DataFusion already has find_root() for exactly this, so the fix is to classify on the root error instead of the wrapper. One-line change plus unit tests covering bare, Shared-wrapped, Context-wrapped-Shared, and non-IO cases.

Closes #2028.

Tested with cargo test -p ballista-core --lib error:: (5 new tests pass) and cargo clippy -p ballista-core --lib is clean. I couldn't run the full cluster chaos harness from #2026 locally, but the unit tests cover the classifier paths directly.

A retryable IO error raised on a join's shared build side arrives at the
FailedTask classifier wrapped in DataFusionError::Shared, so the shallow
matches! on the outermost variant never sees the IoError inside and the
task is marked non-retryable. Classify on find_root() instead so the
retryability decision is based on the root error regardless of wrapping.

@phillipleblanc phillipleblanc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense to me, thanks!

@andygrove andygrove left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andygrove
andygrove merged commit b162708 into apache:main Jul 21, 2026
18 checks passed
andygrove added a commit to andygrove/datafusion-ballista that referenced this pull request Jul 28, 2026
- Rename concurrent_tasks to vcores in chaos-executor for the
  ExecutorProcessConfig change on main
- Ignore executor_killed_after_shuffle_write_is_recovered against apache#2027:
  CI showed the reduce stage hitting the dead executor's FetchFailed
  flattened inside DataFusionError::Shared, so the map stage is never
  resubmitted; the scenario only passes when the kill loses the race
- Ignore both retryable-fault scenarios against apache#2027's error
  flattening: apache#2028 was fixed on main (apache#2119) but the sort-shuffle
  writer refactor now Debug-formats task errors into opaque Execution
  strings before classification, so the injected IoError is
  misclassified as non-retryable under both AQE settings
- Serialize clusters machine-wide with an advisory flock so concurrent
  cargo invocations or binary-parallel runners cannot start two
  clusters at once
- Raise the executor registration deadline from 30s to 120s and attach
  every child process log tail to the timeout error, making the CI
  startup timeouts diagnosable if they recur
- Update the README findings to match
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Retryable IO errors are misclassified as non-retryable when wrapped in DataFusionError::Shared

4 participants