This is an automated email from the ASF dual-hosted git repository. voonhous pushed a commit to branch release-1.2.1 in repository https://gitbox.apache.org/repos/asf/hudi.git
commit d24332afdce4d0ad2c4648cfbe80d516e01c034d Author: Lokesh Jain <[email protected]> AuthorDate: Tue May 26 20:48:15 2026 +0530 [MINOR] Handle cancellation error with HoodieMetadataTableValidator (#18371) --------- Co-authored-by: Lokesh Jain <[email protected]> (cherry picked from commit 516d9e2093a5a10fa98d9e446675e66039db6647) --- .../org/apache/hudi/exception/ExceptionUtil.java | 51 +++++++++++++++++++ .../apache/hudi/exception/TestExceptionUtil.java | 57 ++++++++++++++++++++++ .../utilities/HoodieMetadataTableValidator.java | 3 ++ 3 files changed, 111 insertions(+) diff --git a/hudi-common/src/main/java/org/apache/hudi/exception/ExceptionUtil.java b/hudi-common/src/main/java/org/apache/hudi/exception/ExceptionUtil.java new file mode 100644 index 000000000000..92e2e3cc5356 --- /dev/null +++ b/hudi-common/src/main/java/org/apache/hudi/exception/ExceptionUtil.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hudi.exception; + +import org.apache.hudi.common.util.StringUtils; + +import javax.annotation.Nonnull; + +/** + * Util class for exception analysis. + */ +public final class ExceptionUtil { + private ExceptionUtil() { + } + + /** + * Returns true if error message is contained in any nested exception of provided {@link Throwable}. + */ + public static boolean validateErrorMsg(@Nonnull Throwable t, String errorMsg) { + if (StringUtils.isNullOrEmpty(errorMsg)) { + return false; + } + + Throwable cause = t; + while (cause != null) { + if (cause.getMessage() != null && cause.getMessage().contains(errorMsg)) { + return true; + } + cause = cause.getCause(); + } + + return false; + } +} diff --git a/hudi-common/src/test/java/org/apache/hudi/exception/TestExceptionUtil.java b/hudi-common/src/test/java/org/apache/hudi/exception/TestExceptionUtil.java new file mode 100644 index 000000000000..dba850b1b907 --- /dev/null +++ b/hudi-common/src/test/java/org/apache/hudi/exception/TestExceptionUtil.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hudi.exception; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.apache.hudi.exception.ExceptionUtil.validateErrorMsg; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TestExceptionUtil { + + @Test + void testValidateErrorMsgInNestedCause() { + IOException rootException = new IOException("File not found: data.parquet"); + IllegalArgumentException middleException = new IllegalArgumentException("Invalid argument", rootException); + RuntimeException topException = new RuntimeException("Operation failed", middleException); + + // Check top level exception msg + assertTrue(validateErrorMsg(topException, "File not found")); + assertTrue(validateErrorMsg(topException, "data.parquet")); + // Check nested exception msg + assertTrue(validateErrorMsg(topException, "Invalid argument")); + assertTrue(validateErrorMsg(topException, "Operation failed")); + // Validate no exception matches + assertFalse(validateErrorMsg(topException, "Connection refused")); + } + + @Test + void testValidateErrorMsgWithEmptyMessage() { + RuntimeException exceptionWithMessage = new RuntimeException("Some error"); + assertFalse(validateErrorMsg(exceptionWithMessage, "")); + + RuntimeException exceptionWithoutMessage = new RuntimeException(); + // Empty string should not be found in any message (including null) + assertFalse(validateErrorMsg(exceptionWithoutMessage, "")); + } +} diff --git a/hudi-utilities/src/main/java/org/apache/hudi/utilities/HoodieMetadataTableValidator.java b/hudi-utilities/src/main/java/org/apache/hudi/utilities/HoodieMetadataTableValidator.java index 778cd0090bc7..daf9370c3550 100644 --- a/hudi-utilities/src/main/java/org/apache/hudi/utilities/HoodieMetadataTableValidator.java +++ b/hudi-utilities/src/main/java/org/apache/hudi/utilities/HoodieMetadataTableValidator.java @@ -75,6 +75,7 @@ import org.apache.hudi.common.util.VisibleForTesting; import org.apache.hudi.common.util.collection.Pair; import org.apache.hudi.data.HoodieJavaRDD; import org.apache.hudi.data.HoodieSparkRDDUtils; +import org.apache.hudi.exception.ExceptionUtil; import org.apache.hudi.exception.HoodieException; import org.apache.hudi.exception.HoodieIOException; import org.apache.hudi.exception.HoodieValidationException; @@ -690,6 +691,8 @@ public class HoodieMetadataTableValidator implements Serializable { } catch (SparkException sparkException) { if (sparkException.getCause() instanceof HoodieValidationException) { throw (HoodieValidationException) sparkException.getCause(); + } else if (ExceptionUtil.validateErrorMsg(sparkException, "cancelled because SparkContext was shut down")) { + throw new HoodieException(sparkException); } else { throw new HoodieValidationException("Unexpected spark failure", sparkException); }
