Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to do error handling on custom truffle filesystems #781

Closed
bvollmerOT opened this issue Nov 9, 2023 · 3 comments
Closed

How to do error handling on custom truffle filesystems #781

bvollmerOT opened this issue Nov 9, 2023 · 3 comments

Comments

@bvollmerOT
Copy link

bvollmerOT commented Nov 9, 2023

Hi,
I implemented a custom filesystem and in general it's working fine. But there is an edge case where I'm not sure if it's expected behavior or if I'm missing something. The edge case is using JS code with a module import.

The custom filesystem is implemented against a resource repository which is a database. Part of the filesystem is also some error handling like this:

@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
  String id = path.getFileName().toString();

  if (Files.exists(path)) {
    return FileChannel.open(path, options, attrs);
  }
  final Optional<Script> script = resourceRepo.findObjectById(id);
  if (script.isPresent()) {
    // return file channel ...
  } else {
    throw new IOException("Script not found");
  }
}

The caller code is implemented like this.

try {
  String scriptCode = "import 'NonExistingRepoId';";
  executionContext.eval(Source.newBuilder(scriptLanguage, scriptCode, scriptId + ".mjs").build());
} catch (Exception e) {
  throw new CustomException("Script execution failed", e.getMessage());
}

My expectation was that I'm able to capture the IOException in the caller code. I can see that the exception is thrown and then get wrapped in the GraalVM framework code and at some point it seems like it is swallowed by GraalVM.

@bvollmerOT bvollmerOT changed the title Ho to do error handling on custom truffle filesystems How to do error handling on custom truffle filesystems Nov 9, 2023
@iamstolis
Copy link
Member

It would be nice to provide a simple test-case that reproduces this problem. I tried the following code:

FileSystem fs = new FileSystem() {
    @Override
    public Path parsePath(URI uri) {
        throw new UnsupportedOperationException("Not supported: parsePath(URI)");
    }

    @Override
    public Path parsePath(String path) {
        return Path.of(path);
    }

    @Override
    public void checkAccess(Path path, Set<? extends AccessMode> modes, LinkOption... linkOptions) throws IOException {
    }

    @Override
    public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
        throw new UnsupportedOperationException("Not supported: createDirectory");
    }

    @Override
    public void delete(Path path) throws IOException {
        throw new UnsupportedOperationException("Not supported: delete");
    }

    @Override
    public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
        throw new IOException("Script not found: " + path);
    }

    @Override
    public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
        throw new UnsupportedOperationException("Not supported: newDirectoryStream");
    }

    @Override
    public Path toAbsolutePath(Path path) {
        return path;
    }

    @Override
    public Path toRealPath(Path path, LinkOption... linkOptions) throws IOException {
        return path;
    }

    @Override
    public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
        throw new UnsupportedOperationException("Not supported: readAttributes");
    }
};
IOAccess ioAccess = IOAccess.newBuilder().fileSystem(fs).build();
try (Context context = Context.newBuilder("js").allowIO(ioAccess).build()) {
    String scriptCode = "import 'NonExistingRepoId';";
    context.eval(Source.newBuilder("js", scriptCode, "myModule.mjs").build());
}

and I am getting

Exception in thread "main" Error: Script not found: NonExistingRepoId
	at org.graalvm.polyglot.Context.eval(Context.java:402)

In other words, an exception is thrown as expected/requested.

I have noticed that you edited your report and that the initial version of the source code contained import('NonExistingRepoId'); instead of import 'NonExistingRepoId';. This is a significant difference. No exception is expected in the former case. import(...) is a dynamic import that returns a promise. When something goes wrong then the promise is rejected. You should add catch handler to be notified about the rejection:

import('NonExistingRepoId').catch(e => console.log(e));

Alternatively, you can set js.unhandled-rejections context option to a different value than none (i.e. to throw or warn) to be notified about rejections that are missing catch handler.

@bvollmerOT
Copy link
Author

bvollmerOT commented Nov 13, 2023

@iamstolis
Your observation is correct. I first thought it is only happening with dynamic imports. Later I realized that I didn't really test it with a 'side effect' import or named import. I did another test with a static import and was still able to see the issue, that's why I edited the issue afterwards.

If you say you couldn't reproduce it, I will check it once more.

@bvollmerOT
Copy link
Author

bvollmerOT commented Nov 13, 2023

Conclusion: Static imports are behaving as expected and for dynamic imports, the last paragraph of @iamstolis comment about unhandled rejections was what I was searching for.

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

No branches or pull requests

2 participants