Skip to content

Commit

Permalink
Port asies "eldritch horrors" PR from fabric api (FabricMC/fabric#103)…
Browse files Browse the repository at this point in the history
… to loader in order to fix #339
  • Loading branch information
AlexIIL committed Aug 5, 2023
1 parent ebfa162 commit b8ead42
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.quiltmc.loader.api.QuiltLoader;
import org.quiltmc.loader.impl.filesystem.QuiltClassPath;
import org.quiltmc.loader.impl.game.GameProvider;
import org.quiltmc.loader.impl.util.DeferredInputStream;
import org.quiltmc.loader.impl.util.QuiltLoaderInternal;
import org.quiltmc.loader.impl.util.QuiltLoaderInternalType;
import org.quiltmc.loader.impl.util.SystemProperties;
Expand Down Expand Up @@ -133,6 +134,16 @@ public URL findResource(String name) {
public InputStream getResourceAsStream(String name) {
Objects.requireNonNull(name);

try {
return DeferredInputStream.deferIfNeeded(() -> {
return getResourceAsStream0(name);
});
} catch (IOException e) {
throw new RuntimeException("Failed to fetch the input stream", e);
}
}

private InputStream getResourceAsStream0(String name) {
Path path = paths.findResource(name);
if (path != null) {
try {
Expand Down
107 changes: 107 additions & 0 deletions src/main/java/org/quiltmc/loader/impl/util/DeferredInputStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2016 FabricMC
* Copyright 2023 QuiltMC
*
* Licensed 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.quiltmc.loader.impl.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Callable;

/**
* InputStream deferring to a separate I/O thread to work around
* Thread.interrupt()-related issues in NIO.
*/
@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL)
public class DeferredInputStream extends InputStream {
private final InputStream stream;

public static InputStream deferIfNeeded(Callable<InputStream> streamSupplier) throws IOException {
InputStream stream = DeferredNioExecutionHandler.submit(streamSupplier, DeferredNioExecutionHandler.shouldDefer());
if (stream != null) {
return new DeferredInputStream(stream);
} else {
return null;
}
}

DeferredInputStream(Callable<InputStream> streamSupplier) throws IOException {
stream = DeferredNioExecutionHandler.submit(streamSupplier);

if (stream == null) {
throw new IOException("Something happened while trying to create an InputStream!");
}
}

DeferredInputStream(InputStream stream) throws IOException {
this.stream = stream;

if (this.stream == null) {
throw new IOException("Something happened while trying to create an InputStream!");
}
}

@Override
public int available() throws IOException {
return DeferredNioExecutionHandler.submit(stream::available);
}

@Override
public boolean markSupported() {
return stream.markSupported();
}

@Override
public void mark(int readLimit) {
stream.mark(readLimit);
}

@Override
public void reset() throws IOException {
DeferredNioExecutionHandler.submit(() -> {
stream.reset();
return null;
});
}

@Override
public long skip(long n) throws IOException {
return DeferredNioExecutionHandler.submit(() -> stream.skip(n));
}

@Override
public int read() throws IOException {
return DeferredNioExecutionHandler.submit(stream::read);
}

@Override
public int read(byte[] b) throws IOException {
return DeferredNioExecutionHandler.submit(() -> stream.read(b));
}

@Override
public int read(byte[] b, int offset, int length) throws IOException {
return DeferredNioExecutionHandler.submit(() -> stream.read(b, offset, length));
}

@Override
public void close() throws IOException {
DeferredNioExecutionHandler.submit(() -> {
stream.close();
return null;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2016 FabricMC
* Copyright 2023 QuiltMC
*
* Licensed 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.quiltmc.loader.impl.util;

import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;

@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL)
class DeferredNioExecutionHandler {
// private static final ThreadLocal<Boolean> DEFERRED_REQUIRED = new ThreadLocal<>();
private static final boolean DEFER_REQUESTED = true;// System.getProperty("fabric.resource-loader.deferFilesystemOperations", "false").equalsIgnoreCase("true");
private static ExecutorService EXECUTOR_SERVICE;

public static boolean shouldDefer() {
return DEFER_REQUESTED;
/* Boolean deferRequired = DEFERRED_REQUIRED.get();
if (deferRequired == null) {
deferRequired = false;
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
for (int i = 0; i < elements.length; i++) {
if (elements[i].getClassName().startsWith("paulscode.sound.")) {
deferRequired = true;
break;
}
}
DEFERRED_REQUIRED.set(deferRequired);
}
return deferRequired; */
}

public static <V> V submit(Callable<V> callable, boolean cond) throws IOException {
try {
return cond ? submit(callable) : callable.call();
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException("Exception which should not happen!", e);
}
}

public static <V> V submit(Callable<V> callable) throws IOException {
if (EXECUTOR_SERVICE == null) {
EXECUTOR_SERVICE = Executors.newSingleThreadExecutor(
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread tr = new Thread(r, ("Quilt Deferred I/O Thread"));
tr.setDaemon(true);
return tr;
}
}
);
}

Future<V> future = EXECUTOR_SERVICE.submit(callable);
return getSubmittedFuture(future);
}

static <V> V getSubmittedFuture(Future<V> future) throws IOException {
while (true) {
try {
return future.get();
} catch (ExecutionException e) {
Throwable t = e.getCause();

if (t instanceof IOException) {
throw (IOException) t;
} else {
throw new RuntimeException("ExecutionException which should not happen!", t);
}
} catch (InterruptedException e) {
// keep calm, carry on...
}
}
}
}

0 comments on commit b8ead42

Please sign in to comment.