Plugin remapping

Co-authored-by: Nassim Jahnke <nassim@njahnke.dev>
This commit is contained in:
Jason Penilla
2022-10-29 15:22:32 -07:00
parent 216388dfdf
commit 13e0a1a71e
22 changed files with 1691 additions and 70 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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<>();

View File

@@ -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);
}
}
}
}