This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 083170ad71 [common] Keep unchecked loader failures in FileIO.get
(#10752)
083170ad71 is described below
commit 083170ad71fe01659e457e0d5cc02c007d7a1b76
Author: Etienne Chauchot <[email protected]>
AuthorDate: Mon Oct 26 07:23:01 2026 +0100
[common] Keep unchecked loader failures in FileIO.get (#10752)
---
.../src/main/java/org/apache/paimon/fs/FileIO.java | 29 +++++++++--
.../test/java/org/apache/paimon/fs/FileIOTest.java | 58 ++++++++++++++++++++++
2 files changed, 82 insertions(+), 5 deletions(-)
diff --git a/paimon-common/src/main/java/org/apache/paimon/fs/FileIO.java
b/paimon-common/src/main/java/org/apache/paimon/fs/FileIO.java
index 16efe5f6a6..2863092639 100644
--- a/paimon-common/src/main/java/org/apache/paimon/fs/FileIO.java
+++ b/paimon-common/src/main/java/org/apache/paimon/fs/FileIO.java
@@ -656,12 +656,20 @@ public interface FileIO extends Serializable, Closeable {
+ fallbackIO.getClass().getSimpleName()
+ " also cannot access this path.";
}
+ String firstErrorMsg = "";
+ if (!ioExceptionList.isEmpty()) {
+ Throwable rootCause = ioExceptionList.get(0);
+ while (rootCause.getCause() != null && rootCause.getCause() !=
rootCause) {
+ rootCause = rootCause.getCause();
+ }
+ firstErrorMsg = " First error: " + rootCause;
+ }
UnsupportedSchemeException ex =
new UnsupportedSchemeException(
String.format(
"Could not find a file io implementation
for scheme '%s' in the classpath."
- + "%s %s Hadoop FileSystem also
cannot access this path '%s'.",
- uri.getScheme(), preferMsg, fallbackMsg,
path));
+ + "%s %s Hadoop FileSystem also
cannot access this path '%s'.%s",
+ uri.getScheme(), preferMsg, fallbackMsg,
path, firstErrorMsg));
for (IOException ioException : ioExceptionList) {
ex.addSuppressed(ioException);
}
@@ -704,9 +712,20 @@ public interface FileIO extends Serializable, Closeable {
}
// check access
- FileIO io = fileIO.load(path);
- io.configure(config);
- io.exists(path);
+ try {
+ FileIO io = fileIO.load(path);
+ io.configure(config);
+ io.exists(path);
+ } catch (RuntimeException | LinkageError e) {
+ // Keep unchecked failures (e.g. Hadoop's RuntimeException
wrapping a
+ // ClassNotFoundException when the file system class of the scheme
is missing) so that
+ // the caller reports them together with the failures of the other
loaders, instead
+ // of letting them hide those failures.
+ throw new IOException(
+ String.format(
+ "%s cannot access path '%s'.",
fileIO.getClass().getSimpleName(), path),
+ e);
+ }
return fileIO;
}
}
diff --git a/paimon-common/src/test/java/org/apache/paimon/fs/FileIOTest.java
b/paimon-common/src/test/java/org/apache/paimon/fs/FileIOTest.java
index 6642f7b62a..7482177355 100644
--- a/paimon-common/src/test/java/org/apache/paimon/fs/FileIOTest.java
+++ b/paimon-common/src/test/java/org/apache/paimon/fs/FileIOTest.java
@@ -178,6 +178,34 @@ public class FileIOTest {
assertThat(fileIO).isInstanceOf(RequireOptionsFileIOLoader.MyFileIO.class);
}
+ @Test
+ public void testGetKeepsUncheckedFailuresOfLoaders() {
+ RuntimeException preferFailure =
+ new RuntimeException(
+ new ClassNotFoundException(
+ "Class org.apache.hadoop.fs.s3a.S3AFileSystem
not found"));
+ IOException fallbackFailure = new IOException("The specified bucket
does not exist");
+ CatalogContext context =
+ CatalogContext.create(
+ new Options(),
+ new FailingFileIOLoader(preferFailure),
+ new FailingFileIOLoader(fallbackFailure));
+
+ assertThatThrownBy(() -> FileIO.get(new
Path("failing-scheme://bucket/path"), context))
+ .isInstanceOf(UnsupportedSchemeException.class)
+ .hasMessageContaining(
+ "First error: java.lang.ClassNotFoundException: "
+ + "Class
org.apache.hadoop.fs.s3a.S3AFileSystem not found")
+ .satisfies(
+ e -> {
+ assertThat(e.getSuppressed()[0])
+ .isInstanceOf(IOException.class)
+ .hasMessageContaining("FailingFileIOLoader
cannot access path")
+ .hasCause(preferFailure);
+
assertThat(e.getSuppressed()).contains(fallbackFailure);
+ });
+ }
+
@Test
public void testCopy() throws Exception {
Path srcFile = new Path(tempDir.resolve("src.txt").toUri());
@@ -445,4 +473,34 @@ public class FileIOTest {
return new File(localPath);
}
}
+
+ /** A {@link FileIOLoader} whose {@link FileIO} fails the access check
with a given failure. */
+ private static class FailingFileIOLoader implements FileIOLoader {
+
+ private static final long serialVersionUID = 1L;
+
+ private final Exception failure;
+
+ private FailingFileIOLoader(Exception failure) {
+ this.failure = failure;
+ }
+
+ @Override
+ public String getScheme() {
+ return "failing-scheme";
+ }
+
+ @Override
+ public FileIO load(Path path) {
+ return new LocalFileIO() {
+ @Override
+ public boolean exists(Path path) throws IOException {
+ if (failure instanceof IOException) {
+ throw (IOException) failure;
+ }
+ throw (RuntimeException) failure;
+ }
+ };
+ }
+ }
}