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

andygrove pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-ballista.git


The following commit(s) were added to refs/heads/main by this push:
     new b16270801 see through Shared wrapper when classifying task IO errors 
(#2119)
b16270801 is described below

commit b16270801249a3422e9384ddf4a52bf92e332e87
Author: goingforstudying-ctrl <[email protected]>
AuthorDate: Tue Jul 21 08:46:13 2026 -0400

    see through Shared wrapper when classifying task IO errors (#2119)
    
    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.
    
    Co-authored-by: goingforstudying-ctrl 
<[email protected]>
---
 ballista/core/src/error.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 74 insertions(+), 1 deletion(-)

diff --git a/ballista/core/src/error.rs b/ballista/core/src/error.rs
index b0ce594f3..b5a7a82b0 100644
--- a/ballista/core/src/error.rs
+++ b/ballista/core/src/error.rs
@@ -235,7 +235,7 @@ impl From<BallistaError> for FailedTask {
                 }
             }
             BallistaError::DataFusionError(e)
-                if matches!(*e, DataFusionError::IoError(_)) =>
+                if matches!(e.find_root(), DataFusionError::IoError(_)) =>
             {
                 FailedTask {
                     error: format!("Task failed due to DataFusion IO error: 
{e:?}"),
@@ -256,3 +256,76 @@ impl From<BallistaError> for FailedTask {
 }
 
 impl Error for BallistaError {}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::sync::Arc;
+
+    fn io_failed_task(e: BallistaError) -> FailedTask {
+        FailedTask::from(e)
+    }
+
+    #[test]
+    fn bare_datafusion_io_error_is_retryable() {
+        let e = 
BallistaError::DataFusionError(Box::new(DataFusionError::IoError(
+            io::Error::new(io::ErrorKind::ConnectionReset, "connection reset"),
+        )));
+        let task = io_failed_task(e);
+        assert!(task.retryable);
+        assert!(matches!(task.failed_reason, Some(FailedReason::IoError(_))));
+    }
+
+    #[test]
+    fn shared_wrapped_io_error_is_retryable() {
+        // Errors from a join's shared build side arrive as 
Shared(Arc<IoError>);
+        // the classifier must see through the wrapper or it will not retry a
+        // transient IO failure.
+        let inner = DataFusionError::IoError(io::Error::new(
+            io::ErrorKind::ConnectionReset,
+            "connection reset",
+        ));
+        let shared = DataFusionError::Shared(Arc::new(inner));
+        let e = BallistaError::DataFusionError(Box::new(shared));
+        let task = io_failed_task(e);
+        assert!(task.retryable);
+        assert!(matches!(task.failed_reason, Some(FailedReason::IoError(_))));
+    }
+
+    #[test]
+    fn context_wrapped_shared_io_error_is_retryable() {
+        let inner = DataFusionError::IoError(io::Error::other("s3 timeout"));
+        let shared = DataFusionError::Shared(Arc::new(inner));
+        let ctx = shared.context("reading join build side");
+        let e = BallistaError::DataFusionError(Box::new(ctx));
+        let task = io_failed_task(e);
+        assert!(task.retryable);
+        assert!(matches!(task.failed_reason, Some(FailedReason::IoError(_))));
+    }
+
+    #[test]
+    fn non_io_datafusion_error_stays_non_retryable() {
+        let e = BallistaError::DataFusionError(Box::new(DataFusionError::Plan(
+            "bad plan".to_string(),
+        )));
+        let task = io_failed_task(e);
+        assert!(!task.retryable);
+        assert!(matches!(
+            task.failed_reason,
+            Some(FailedReason::ExecutionError(_))
+        ));
+    }
+
+    #[test]
+    fn shared_wrapped_non_io_error_stays_non_retryable() {
+        let inner = DataFusionError::Plan("bad plan".to_string());
+        let shared = DataFusionError::Shared(Arc::new(inner));
+        let e = BallistaError::DataFusionError(Box::new(shared));
+        let task = io_failed_task(e);
+        assert!(!task.retryable);
+        assert!(matches!(
+            task.failed_reason,
+            Some(FailedReason::ExecutionError(_))
+        ));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to