Skip to content

Commit

Permalink
[GR-26421] Add support to Truffle contexts for tracking auto-closeabl…
Browse files Browse the repository at this point in the history
…e resources, and close them when the context gets disposed.

PullRequest: graal/8618
  • Loading branch information
tzezula committed Apr 8, 2021
2 parents 9070707 + fc6ae36 commit e8ea25a
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 7 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,16 @@ jobs:
run: |
mkdir jdk-dl
${MX_PATH}/mx fetch-jdk --java-distribution ${JDK} --to jdk-dl --alias ${JAVA_HOME}
- name: Update dependency cache
if: ${{ contains(matrix.env.GATE, 'debug') || contains(matrix.env.GATE, 'style') }}
run: sudo apt update
- name: Debug dependencies
if: ${{ contains(matrix.env.GATE, 'debug') }}
run: sudo apt install gdb
- name: Style dependencies
if: ${{ contains(matrix.env.GATE, 'style') }}
run: |
sudo apt install python-pip
sudo apt install python-pip python-setuptools
sudo pip install astroid==1.1.0
sudo pip install pylint==1.1.0
- name: Build GraalVM and run gate
Expand Down
1 change: 1 addition & 0 deletions truffle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This changelog summarizes major changes between Truffle versions relevant to lan
## Version 21.2.0
* Added `TypeDescriptor.subtract(TypeDescriptor)` creating a new `TypeDescriptor` by removing the given type from a union or intersection type.
* Added `CompilerDirectives.blackhole(value)` which can be helpful for benchmarking.
* Added `TruffleLanguage#Env.registerOnDispose(Closeable)` registering `Closeable`s for automatic close on context dispose.

## Version 21.1.0
* Added methods into `Instrumenter` that create bindings to be attached later on. Added `EventBinding.attach()` method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,12 @@
import com.oracle.truffle.api.test.polyglot.TruffleFileTest.DuplicateMimeTypeLanguage1.Language1Detector;
import com.oracle.truffle.api.test.polyglot.TruffleFileTest.DuplicateMimeTypeLanguage2.Language2Detector;
import com.oracle.truffle.api.test.polyglot.FileSystemsTest.ForwardingFileSystem;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
Expand All @@ -64,6 +68,7 @@
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.Charset;
import java.nio.file.AccessMode;
Expand All @@ -78,19 +83,31 @@
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.io.FileSystem;
import org.junit.After;
import org.junit.AfterClass;
Expand Down Expand Up @@ -618,6 +635,121 @@ public void testGetTruffleFileInternalDeniedIO() throws IOException {
assertEquals(res.getName(), new String(res.readAllBytes()).trim());
}

@Test
@SuppressWarnings("unused")
public void testChannelClose() throws IOException {
Path p = Files.createTempDirectory("channelClose");
Path read1 = Files.createFile(p.resolve("read1"));
Path read2 = Files.createFile(p.resolve("read2"));
Path read3 = Files.createFile(p.resolve("read3"));
Path read4 = Files.createFile(p.resolve("read4"));
Path write1 = Files.createFile(p.resolve("write1"));
Path write2 = Files.createFile(p.resolve("write2"));
Path write3 = Files.createFile(p.resolve("write3"));
Path write4 = Files.createFile(p.resolve("write4"));
try {
CheckCloseFileSystem fs = new CheckCloseFileSystem();
setupEnv(Context.newBuilder().fileSystem(fs).allowIO(true).build());
assertEquals(0, fs.openFileCount);
SeekableByteChannel readByteChannel1 = languageEnv.getPublicTruffleFile(read1.toString()).newByteChannel(EnumSet.of(StandardOpenOption.READ));
assertEquals(1, fs.openFileCount);
SeekableByteChannel readByteChannel2 = languageEnv.getPublicTruffleFile(read2.toString()).newByteChannel(EnumSet.of(StandardOpenOption.READ));
languageEnv.registerOnDispose(readByteChannel2);
assertEquals(2, fs.openFileCount);
InputStream inputStream1 = languageEnv.getPublicTruffleFile(read3.toString()).newInputStream();
assertEquals(3, fs.openFileCount);
InputStream inputStream2 = languageEnv.getPublicTruffleFile(read4.toString()).newInputStream();
languageEnv.registerOnDispose(inputStream2);
assertEquals(4, fs.openFileCount);
SeekableByteChannel writeByteChannel1 = languageEnv.getPublicTruffleFile(write1.toString()).newByteChannel(EnumSet.of(StandardOpenOption.WRITE));
assertEquals(5, fs.openFileCount);
SeekableByteChannel writeByteChannel2 = languageEnv.getPublicTruffleFile(write2.toString()).newByteChannel(EnumSet.of(StandardOpenOption.WRITE));
languageEnv.registerOnDispose(writeByteChannel2);
assertEquals(6, fs.openFileCount);
OutputStream outputStream1 = languageEnv.getPublicTruffleFile(write3.toString()).newOutputStream();
assertEquals(7, fs.openFileCount);
OutputStream outputStream2 = languageEnv.getPublicTruffleFile(write4.toString()).newOutputStream();
languageEnv.registerOnDispose(outputStream2);
assertEquals(8, fs.openFileCount);
readByteChannel1.close();
assertEquals(7, fs.openFileCount);
inputStream1.close();
assertEquals(6, fs.openFileCount);
writeByteChannel1.close();
assertEquals(5, fs.openFileCount);
outputStream1.close();
assertEquals(4, fs.openFileCount);
context.close();
assertEquals(0, fs.openFileCount);
} finally {
delete(p);
}
}

@Test
@SuppressWarnings("unused")
public void testDirClose() throws IOException {
Path p = Files.createTempDirectory("channelClose");
Path dir1 = Files.createDirectory(p.resolve("read1"));
Path dir2 = Files.createDirectory(p.resolve("read2"));
try {
CheckCloseFileSystem fs = new CheckCloseFileSystem();
setupEnv(Context.newBuilder().fileSystem(fs).allowIO(true).build());
assertEquals(0, fs.openDirCount);
DirectoryStream<TruffleFile> dirStream1 = languageEnv.getPublicTruffleFile(dir1.toString()).newDirectoryStream();
assertEquals(1, fs.openDirCount);
DirectoryStream<TruffleFile> dirStream2 = languageEnv.getPublicTruffleFile(dir2.toString()).newDirectoryStream();
languageEnv.registerOnDispose(dirStream2);
assertEquals(2, fs.openDirCount);
dirStream1.close();
assertEquals(1, fs.openDirCount);
context.close();
assertEquals(0, fs.openDirCount);
} finally {
delete(p);
}
}

@Test
public void testIOExceptionFromClose() {
TestHandler handler = new TestHandler();
setupEnv(Context.newBuilder().allowAllAccess(true).logHandler(handler).build());
Closeable closeable = new Closeable() {
@Override
public void close() throws IOException {
throw new IOException();
}
};
languageEnv.registerOnDispose(closeable);
context.close();
Optional<LogRecord> record = handler.findRecordByMessage("Failed to close.*");
assertTrue(record.isPresent());
assertEquals(Level.WARNING, record.map(LogRecord::getLevel).get());
assertEquals("engine", record.map(LogRecord::getLoggerName).get());
}

@Test
public void testUncheckedExceptionFromClose() {
TestHandler handler = new TestHandler();
setupEnv(Context.newBuilder().allowAllAccess(true).logHandler(handler).build());
Closeable closeable = new Closeable() {
@Override
public void close() throws IOException {
throw new RuntimeException();
}
};
languageEnv.registerOnDispose(closeable);
try {
assertFails(() -> context.close(), PolyglotException.class, (pe) -> {
assertTrue(pe.isInternalError());
});
} finally {
context = null;
}
Optional<LogRecord> record = handler.findRecordByMessage("Failed to close.*");
assertFalse(record.isPresent());
}

private static void delete(Path path) throws IOException {
if (Files.isDirectory(path)) {
try (DirectoryStream<Path> dir = Files.newDirectoryStream(path)) {
Expand Down Expand Up @@ -901,4 +1033,127 @@ public boolean test(TruffleFile truffleFile) {
return truffleFile.startsWith(stdLibFolder);
}
}

private static final class CheckCloseFileSystem extends ForwardingFileSystem {

private int openFileCount;
private int openDirCount;

CheckCloseFileSystem() {
super(FileSystem.newDefaultFileSystem());
}

@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
return new CheckCloseChannel(super.newByteChannel(path, options, attrs));
}

@Override
public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
return new CheckCloseDirectoryStream(super.newDirectoryStream(dir, filter));
}

private final class CheckCloseChannel implements SeekableByteChannel {

private SeekableByteChannel delegate;

CheckCloseChannel(SeekableByteChannel delegate) {
this.delegate = Objects.requireNonNull(delegate);
openFileCount++;
}

@Override
public int read(ByteBuffer byteBuffer) throws IOException {
return delegate.read(byteBuffer);
}

@Override
public int write(ByteBuffer byteBuffer) throws IOException {
return delegate.write(byteBuffer);
}

@Override
public long position() throws IOException {
return delegate.position();
}

@Override
public SeekableByteChannel position(long l) throws IOException {
delegate.position(l);
return this;
}

@Override
public long size() throws IOException {
return delegate.size();
}

@Override
public SeekableByteChannel truncate(long l) throws IOException {
delegate.truncate(l);
return this;
}

@Override
public boolean isOpen() {
return delegate.isOpen();
}

@Override
public void close() throws IOException {
try {
delegate.close();
} finally {
openFileCount--;
}
}
}

private final class CheckCloseDirectoryStream implements DirectoryStream<Path> {

private final DirectoryStream<Path> delegate;

CheckCloseDirectoryStream(DirectoryStream<Path> delegate) {
this.delegate = Objects.requireNonNull(delegate);
openDirCount++;
}

@Override
public Iterator<Path> iterator() {
return delegate.iterator();
}

@Override
public void close() throws IOException {
try {
delegate.close();
} finally {
openDirCount--;
}
}
}
}

private static final class TestHandler extends Handler {

private final List<LogRecord> records = new ArrayList<>();

@Override
public void publish(LogRecord record) {
records.add(record);
}

@Override
public void flush() {
}

@Override
public void close() {
}

Optional<LogRecord> findRecordByMessage(String regex) {
Pattern pattern = Pattern.compile(regex);
return records.stream().filter((r) -> r.getMessage() != null && pattern.matcher(r.getMessage()).matches()).findAny();
}
}
}
1 change: 1 addition & 0 deletions truffle/src/com.oracle.truffle.api/snapshot.sigtest
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ meth public java.util.concurrent.Future<java.lang.Void> submitThreadLocal(java.l
meth public org.graalvm.options.OptionValues getOptions()
meth public void addToHostClassPath(com.oracle.truffle.api.TruffleFile)
meth public void exportSymbol(java.lang.String,java.lang.Object)
meth public void registerOnDispose(java.io.Closeable)
meth public void registerService(java.lang.Object)
meth public void setCurrentWorkingDirectory(com.oracle.truffle.api.TruffleFile)
supr java.lang.Object
Expand Down
Loading

0 comments on commit e8ea25a

Please sign in to comment.