@@ -0,0 +1,96 @@
|
||||
package io.papermc.paper.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import java.nio.file.CopyOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.function.Consumer;
|
||||
import org.spongepowered.configurate.util.CheckedConsumer;
|
||||
|
||||
// Stripped down version of https://github.com/jpenilla/squaremap/blob/7d7994b4096e5fc61364ea2d87e9aa4e14edf5c6/common/src/main/java/xyz/jpenilla/squaremap/common/util/FileUtil.java
|
||||
public final class AtomicFiles {
|
||||
|
||||
private AtomicFiles() {
|
||||
}
|
||||
|
||||
public static void atomicWrite(final Path path, final CheckedConsumer<Path, IOException> op) throws IOException {
|
||||
final Path tmp = tempFile(path);
|
||||
|
||||
try {
|
||||
op.accept(tmp);
|
||||
atomicMove(tmp, path, true);
|
||||
} catch (final IOException ex) {
|
||||
try {
|
||||
Files.deleteIfExists(tmp);
|
||||
} catch (final IOException ex1) {
|
||||
ex.addSuppressed(ex1);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private static Path tempFile(final Path path) {
|
||||
return path.resolveSibling("." + System.nanoTime() + "-" + ThreadLocalRandom.current().nextInt() + "-" + path.getFileName().toString() + ".tmp"); }
|
||||
|
||||
@SuppressWarnings("BusyWait") // not busy waiting
|
||||
public static void atomicMove(final Path from, final Path to, final boolean replaceExisting) throws IOException {
|
||||
final int maxRetries = 2;
|
||||
|
||||
try {
|
||||
atomicMoveIfPossible(from, to, replaceExisting);
|
||||
} catch (final AccessDeniedException ex) {
|
||||
// Sometimes because of file locking this will fail... Let's just try again and hope for the best
|
||||
// Thanks Windows!
|
||||
int retries = 1;
|
||||
while (true) {
|
||||
try {
|
||||
// Pause for a bit
|
||||
Thread.sleep(10L * retries);
|
||||
atomicMoveIfPossible(from, to, replaceExisting);
|
||||
break; // success
|
||||
} catch (final AccessDeniedException ex1) {
|
||||
ex.addSuppressed(ex1);
|
||||
if (retries == maxRetries) {
|
||||
throw ex;
|
||||
}
|
||||
} catch (final InterruptedException interruptedException) {
|
||||
ex.addSuppressed(interruptedException);
|
||||
Thread.currentThread().interrupt();
|
||||
throw ex;
|
||||
}
|
||||
++retries;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void atomicMoveIfPossible(final Path from, final Path to, final boolean replaceExisting) throws IOException {
|
||||
final CopyOption[] options = replaceExisting
|
||||
? new CopyOption[]{StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING}
|
||||
: new CopyOption[]{StandardCopyOption.ATOMIC_MOVE};
|
||||
|
||||
try {
|
||||
Files.move(from, to, options);
|
||||
} catch (final AtomicMoveNotSupportedException ex) {
|
||||
Files.move(from, to, replaceExisting ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{});
|
||||
}
|
||||
}
|
||||
|
||||
private static <T, X extends Throwable> Consumer<T> sneaky(final CheckedConsumer<T, X> consumer) {
|
||||
return t -> {
|
||||
try {
|
||||
consumer.accept(t);
|
||||
} catch (final Throwable thr) {
|
||||
rethrow(thr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <X extends Throwable> RuntimeException rethrow(final Throwable t) throws X {
|
||||
throw (X) t;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.papermc.paper.util;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class Hashing {
|
||||
private Hashing() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the provided {@link InputStream} using SHA-256. Stream will be closed.
|
||||
*
|
||||
* @param stream input stream
|
||||
* @return SHA-256 hash string
|
||||
*/
|
||||
public static String sha256(final InputStream stream) {
|
||||
try (stream) {
|
||||
return com.google.common.hash.Hashing.sha256().hashBytes(IOUtils.toByteArray(stream)).toString().toUpperCase(Locale.ROOT);
|
||||
} catch (final IOException ex) {
|
||||
throw new RuntimeException("Failed to take hash of InputStream", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the provided file using SHA-256.
|
||||
*
|
||||
* @param file file
|
||||
* @return SHA-256 hash string
|
||||
*/
|
||||
public static String sha256(final Path file) {
|
||||
if (!Files.isRegularFile(file)) {
|
||||
throw new IllegalArgumentException("'" + file + "' is not a regular file!");
|
||||
}
|
||||
final HashCode hash;
|
||||
try {
|
||||
hash = com.google.common.io.Files.asByteSource(file.toFile()).hash(com.google.common.hash.Hashing.sha256());
|
||||
} catch (final IOException ex) {
|
||||
throw new RuntimeException("Failed to take hash of file '" + file + "'", ex);
|
||||
}
|
||||
return hash.toString().toUpperCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.papermc.paper.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.jar.Manifest;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class MappingEnvironment {
|
||||
private static final @Nullable String MAPPINGS_HASH = readMappingsHash();
|
||||
private static final boolean REOBF = checkReobf();
|
||||
|
||||
private MappingEnvironment() {
|
||||
}
|
||||
|
||||
public static boolean reobf() {
|
||||
return REOBF;
|
||||
}
|
||||
|
||||
public static boolean hasMappings() {
|
||||
return MAPPINGS_HASH != null;
|
||||
}
|
||||
|
||||
public static InputStream mappingsStream() {
|
||||
return Objects.requireNonNull(mappingsStreamIfPresent(), "Missing mappings!");
|
||||
}
|
||||
|
||||
public static @Nullable InputStream mappingsStreamIfPresent() {
|
||||
return MappingEnvironment.class.getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny");
|
||||
}
|
||||
|
||||
public static String mappingsHash() {
|
||||
return Objects.requireNonNull(MAPPINGS_HASH, "MAPPINGS_HASH");
|
||||
}
|
||||
|
||||
private static @Nullable String readMappingsHash() {
|
||||
final @Nullable Manifest manifest = JarManifests.manifest(MappingEnvironment.class);
|
||||
if (manifest != null) {
|
||||
final Object hash = manifest.getMainAttributes().getValue("Included-Mappings-Hash");
|
||||
if (hash != null) {
|
||||
return hash.toString();
|
||||
}
|
||||
}
|
||||
|
||||
final @Nullable InputStream stream = mappingsStreamIfPresent();
|
||||
if (stream == null) {
|
||||
return null;
|
||||
}
|
||||
return Hashing.sha256(stream);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private static boolean checkReobf() {
|
||||
final Class<?> clazz = MobCategory.class;
|
||||
if (clazz.getSimpleName().equals("MobCategory")) {
|
||||
return false;
|
||||
} else if (clazz.getSimpleName().equals("EnumCreatureType")) {
|
||||
return true;
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
@@ -80,10 +80,10 @@ public enum ObfHelper {
|
||||
}
|
||||
|
||||
private static @Nullable Set<ClassMapping> loadMappingsIfPresent() {
|
||||
try (final @Nullable InputStream mappingsInputStream = ObfHelper.class.getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) {
|
||||
if (mappingsInputStream == null) {
|
||||
return null;
|
||||
}
|
||||
if (!MappingEnvironment.hasMappings()) {
|
||||
return null;
|
||||
}
|
||||
try (final InputStream mappingsInputStream = MappingEnvironment.mappingsStream()) {
|
||||
final IMappingFile mappings = IMappingFile.load(mappingsInputStream); // Mappings are mojang->spigot
|
||||
final Set<ClassMapping> classes = new HashSet<>();
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package io.papermc.paper.util.concurrent;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Utilities for scaling thread pools.
|
||||
*
|
||||
* @see <a href="https://medium.com/@uditharosha/java-scale-first-executorservice-4245a63222df">Java Scale First ExecutorService — A myth or a reality</a>
|
||||
*/
|
||||
public final class ScalingThreadPool {
|
||||
private ScalingThreadPool() {
|
||||
}
|
||||
|
||||
public static RejectedExecutionHandler defaultReEnqueuePolicy() {
|
||||
return reEnqueuePolicy(new ThreadPoolExecutor.AbortPolicy());
|
||||
}
|
||||
|
||||
public static RejectedExecutionHandler reEnqueuePolicy(final RejectedExecutionHandler original) {
|
||||
return new ReEnqueuePolicy(original);
|
||||
}
|
||||
|
||||
public static <E> BlockingQueue<E> createUnboundedQueue() {
|
||||
return new Queue<>();
|
||||
}
|
||||
|
||||
public static <E> BlockingQueue<E> createQueue(final int capacity) {
|
||||
return new Queue<>(capacity);
|
||||
}
|
||||
|
||||
private static final class Queue<E> extends LinkedBlockingQueue<E> {
|
||||
private final AtomicInteger idleThreads = new AtomicInteger(0);
|
||||
|
||||
private Queue() {
|
||||
super();
|
||||
}
|
||||
|
||||
private Queue(final int capacity) {
|
||||
super(capacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean offer(final E e) {
|
||||
return this.idleThreads.get() > 0 && super.offer(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E take() throws InterruptedException {
|
||||
this.idleThreads.incrementAndGet();
|
||||
try {
|
||||
return super.take();
|
||||
} finally {
|
||||
this.idleThreads.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E poll(final long timeout, final TimeUnit unit) throws InterruptedException {
|
||||
this.idleThreads.incrementAndGet();
|
||||
try {
|
||||
return super.poll(timeout, unit);
|
||||
} finally {
|
||||
this.idleThreads.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(final E e) {
|
||||
return super.offer(e);
|
||||
}
|
||||
}
|
||||
|
||||
private record ReEnqueuePolicy(RejectedExecutionHandler originalHandler) implements RejectedExecutionHandler {
|
||||
@Override
|
||||
public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) {
|
||||
if (!executor.getQueue().add(r)) {
|
||||
this.originalHandler.rejectedExecution(r, executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user