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

Stop the JVM from printing a ClosedChannelException stacktrace for each entry in a zip #1213

Open
wants to merge 2 commits into
base: dev/1.8
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/main/java/net/fabricmc/loom/util/FileSystemUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ public String readString(String path) throws IOException {

@Override
public void close() throws IOException {
// JDK-8316882, a separate JDK bug where a zip FS cannot be closed on an interrupted thread
// Doing so will fail, after a stack trace is printed for each entry of the zip
// TODO is there a better way to know if the underlying file channel is closed?
if (Thread.currentThread().isInterrupted()) {
// We leak here as we never actually free the zip FS, but this is the best we can do
// Lets assume the JVM effectively fucked here, so throw a UnrecoverableZipException forcing it to exit
// When a build is canceled a new Gradle daemon will be started, so this is not a big deal
throw new UnrecoverableZipException("Cannot close zip FS on interrupted thread", new InterruptedException());
}

try {
reference.close();
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ public static void tryStopGradleDaemon(Project project) {

@VisibleForTesting
public static boolean stopWhenIdle(Project project) {
// Clear the interrupted flag if set.
Thread.interrupted();

DaemonInfo daemonInfo = findCurrentDaemon(project);

if (daemonInfo == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package net.fabricmc.loom.test.unit

import java.nio.channels.ClosedChannelException
import java.nio.file.FileSystem
import java.nio.file.FileSystemAlreadyExistsException
import java.nio.file.FileSystemException
Expand All @@ -34,7 +35,7 @@ import java.nio.file.Path

import spock.lang.Specification

// Test to prove https://bugs.openjdk.org/browse/JDK-8291712
// Test to prove https://bugs.openjdk.org/browse/JDK-8291712 and https://bugs.openjdk.org/browse/JDK-8316882
// If this test starts failing on a new JDK, it is likely that the bug has been fixed!
class ClosedZipFSReproducer extends Specification {
def "JDK-8291712"() {
Expand Down Expand Up @@ -70,6 +71,33 @@ class ClosedZipFSReproducer extends Specification {
!fs.isOpen()
}

def "JDK-8316882"() {
when:
Path tempDir = Files.createTempDirectory("test")
Path zipFile = tempDir.resolve("example.zip")

// Create a new ZipFileSystem, and write a file to it
openZipFS(zipFile, true).withCloseable {
Files.writeString(it.getPath("test.txt"), "Hello, World!")
}

// Open the existing ZipFileSystem, interrupt the thread before reading the file
def fs = openZipFS(zipFile, false)

Thread.currentThread().interrupt()
Files.readString(fs.getPath("test.txt"))

// Close then unexpectedly throws ClosedChannelException
fs.close()

then:
thrown(ClosedChannelException)

// Reset the interrupt status
Thread.interrupted()
!Thread.currentThread().isInterrupted()
}

private static FileSystem openZipFS(Path path, boolean create) throws IOException {
URI uri = toJarUri(path)
try {
Expand Down
33 changes: 33 additions & 0 deletions src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,39 @@ class ZipUtilsTest extends Specification {
thrown FileSystemUtil.UnrecoverableZipException
}

// Also see: ClosedZipFSReproducer
def "interrupted thread"() {
given:
def dir = File.createTempDir()
def zip = File.createTempFile("loom-zip-test", ".zip").toPath()
new File(dir, "test.json").text = """
{
"test": "This is a test of transforming"
}
"""
ZipUtils.pack(dir.toPath(), zip)

when:

ZipUtils.transformJson(JsonObject.class, zip, "test.json") { json ->
Thread.currentThread().interrupt()

json
}

then:
thrown FileSystemUtil.UnrecoverableZipException

// Reset the interrupt status
Thread.currentThread().isInterrupted()
Thread.interrupted()
!Thread.currentThread().isInterrupted()

cleanup:
// Cleanup after the mess we made.
FileSystemUtil.getJarFileSystem(zip, false).close()
}

def "reprocess uncompressed"() {
given:
// Create a reproducible input zip
Expand Down
Loading