PaperMC/paper-server/patches/sources/net/minecraft/Util.java.patch
Spottedleaf 48be22a63e Use forkjoin thread pool for background executor
ForkJoin thread pools are specially handled with CompletableFuture.
Specifically, join()/get() calls will allow work stealing from other
fork join threads.

This fixes a deadlock where the worldgen worker is waiting on the
I/O worker.
2024-12-16 08:31:15 -08:00

125 lines
6.1 KiB
Diff

--- a/net/minecraft/Util.java
+++ b/net/minecraft/Util.java
@@ -92,9 +_,26 @@
private static final int DEFAULT_MAX_THREADS = 255;
private static final int DEFAULT_SAFE_FILE_OPERATION_RETRIES = 10;
private static final String MAX_THREADS_SYSTEM_PROPERTY = "max.bg.threads";
- private static final TracingExecutor BACKGROUND_EXECUTOR = makeExecutor("Main");
+ private static final TracingExecutor BACKGROUND_EXECUTOR = makeExecutor("Main", -1); // Paper - Perf: add priority
private static final TracingExecutor IO_POOL = makeIoExecutor("IO-Worker-", false);
+ public static final TracingExecutor DIMENSION_DATA_IO_POOL = makeExtraIoExecutor("Dimension-Data-IO-Worker-"); // Paper - Separate dimension data IO pool
private static final TracingExecutor DOWNLOAD_POOL = makeIoExecutor("Download-", true);
+ // Paper start - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread
+ public static final ExecutorService PROFILE_EXECUTOR = Executors.newFixedThreadPool(2, new java.util.concurrent.ThreadFactory() {
+
+ private final AtomicInteger count = new AtomicInteger();
+
+ @Override
+ public Thread newThread(Runnable run) {
+ Thread ret = new Thread(run);
+ ret.setName("Profile Lookup Executor #" + this.count.getAndIncrement());
+ ret.setUncaughtExceptionHandler((Thread thread, Throwable throwable) -> {
+ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
+ });
+ return ret;
+ }
+ });
+ // Paper end - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread
private static final DateTimeFormatter FILENAME_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss", Locale.ROOT);
public static final int LINEAR_LOOKUP_THRESHOLD = 8;
private static final Set<String> ALLOWED_UNTRUSTED_LINK_PROTOCOLS = Set.of("http", "https");
@@ -135,7 +_,7 @@
}
public static long getNanos() {
- return timeSource.getAsLong();
+ return System.nanoTime(); // Paper
}
public static long getEpochMillis() {
@@ -146,9 +_,10 @@
return FILENAME_DATE_TIME_FORMATTER.format(ZonedDateTime.now());
}
- private static TracingExecutor makeExecutor(String name) {
+ private static TracingExecutor makeExecutor(String name, final int priorityModifier) { // Paper - Perf: add priority
int i = maxAllowedExecutorThreads();
- ExecutorService directExecutorService;
+ // Paper start - Perf: use simpler thread pool that allows 1 thread and reduce worldgen thread worker count for low core count CPUs
+ final ExecutorService directExecutorService;
if (i <= 0) {
directExecutorService = MoreExecutors.newDirectExecutorService();
} else {
@@ -173,16 +_,30 @@
super.onTermination(throwOnTermination);
}
};
+ forkJoinWorkerThread.setPriority(Thread.NORM_PRIORITY + priorityModifier); // Paper - Deprioritize over main
forkJoinWorkerThread.setName(string);
return forkJoinWorkerThread;
- }, Util::onThreadException, true);
+ }, Util::onThreadException, true, 0, Integer.MAX_VALUE, 1, null, 365, TimeUnit.DAYS); // Paper - do not expire threads
}
return new TracingExecutor(directExecutorService);
}
public static int maxAllowedExecutorThreads() {
- return Mth.clamp(Runtime.getRuntime().availableProcessors() - 1, 1, getMaxThreads());
+ // Paper start - Perf: use simpler thread pool that allows 1 thread and reduce worldgen thread worker count for low core count CPUs
+ final int cpus = Runtime.getRuntime().availableProcessors() / 2;
+ int maxExecutorThreads;
+ if (cpus <= 4) {
+ maxExecutorThreads = cpus <= 2 ? 1 : 2;
+ } else if (cpus <= 8) {
+ // [5, 8]
+ maxExecutorThreads = Math.max(3, cpus - 2);
+ } else {
+ maxExecutorThreads = cpus * 2 / 3;
+ }
+ maxExecutorThreads = Math.min(8, maxExecutorThreads);
+ return Integer.getInteger("Paper.WorkerThreadCount", maxExecutorThreads);
+ // Paper end - Perf: use simpler thread pool that allows 1 thread and reduce worldgen thread worker count for low core count CPUs
}
private static int getMaxThreads() {
@@ -233,6 +_,21 @@
}));
}
+ // Paper start - Separate dimension data IO pool
+ private static TracingExecutor makeExtraIoExecutor(String namePrefix) {
+ AtomicInteger atomicInteger = new AtomicInteger(1);
+ return new TracingExecutor(Executors.newFixedThreadPool(4, runnable -> {
+ Thread thread = new Thread(runnable);
+ String string2 = namePrefix + atomicInteger.getAndIncrement();
+ TracyClient.setThreadName(string2, namePrefix.hashCode());
+ thread.setName(string2);
+ thread.setDaemon(false);
+ thread.setUncaughtExceptionHandler(Util::onThreadException);
+ return thread;
+ }));
+ }
+ // Paper end - Separate dimension data IO pool
+
public static void throwAsRuntime(Throwable throwable) {
throw throwable instanceof RuntimeException ? (RuntimeException)throwable : new RuntimeException(throwable);
}
@@ -1075,16 +_,7 @@
}
public void openUri(URI uri) {
- try {
- Process process = AccessController.doPrivileged(
- (PrivilegedExceptionAction<Process>)(() -> Runtime.getRuntime().exec(this.getOpenUriArguments(uri)))
- );
- process.getInputStream().close();
- process.getErrorStream().close();
- process.getOutputStream().close();
- } catch (IOException | PrivilegedActionException var3) {
- Util.LOGGER.error("Couldn't open location '{}'", uri, var3);
- }
+ throw new IllegalStateException("This method is not useful on dedicated servers."); // Paper - Fix warnings on build by removing client-only code
}
public void openFile(File file) {