From 4ff7d4c5549f80a5f0ed23262771b3c1a55ceb5d Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 28 Mar 2026 21:43:09 -0400 Subject: [PATCH] Allow a single low-priority worker thread when cause_lag_by_disabling_threads is enabled On a system with few cores, we should still benefit from using one low-priority background thread for worldgen, because it avoids the server thread stopping to handle it itself. The thread will be blocked from progressing while higher-priority work (e.g. rendering or server ticking) is in progress. --- .../UtilMixin.java | 4 +- .../modernfix/util/DirectExecutorService.java | 43 ------------- .../util/SingleThreadedWorkerService.java | 63 +++++++++++++++++++ 3 files changed, 65 insertions(+), 45 deletions(-) delete mode 100644 src/main/java/org/embeddedt/modernfix/util/DirectExecutorService.java create mode 100644 src/main/java/org/embeddedt/modernfix/util/SingleThreadedWorkerService.java diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/feature/cause_lag_by_disabling_threads/UtilMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/feature/cause_lag_by_disabling_threads/UtilMixin.java index a8b452bd..a9d494f4 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/feature/cause_lag_by_disabling_threads/UtilMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/feature/cause_lag_by_disabling_threads/UtilMixin.java @@ -1,7 +1,7 @@ package org.embeddedt.modernfix.common.mixin.feature.cause_lag_by_disabling_threads; import net.minecraft.Util; -import org.embeddedt.modernfix.util.DirectExecutorService; +import org.embeddedt.modernfix.util.SingleThreadedWorkerService; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mutable; @@ -12,5 +12,5 @@ import java.util.concurrent.ExecutorService; @Mixin(Util.class) public class UtilMixin { @Shadow @Final @Mutable - private static final ExecutorService BACKGROUND_EXECUTOR = new DirectExecutorService(); + private static final ExecutorService BACKGROUND_EXECUTOR = new SingleThreadedWorkerService(); } diff --git a/src/main/java/org/embeddedt/modernfix/util/DirectExecutorService.java b/src/main/java/org/embeddedt/modernfix/util/DirectExecutorService.java deleted file mode 100644 index 83b5d0aa..00000000 --- a/src/main/java/org/embeddedt/modernfix/util/DirectExecutorService.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.embeddedt.modernfix.util; - -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.concurrent.AbstractExecutorService; -import java.util.concurrent.TimeUnit; - -public class DirectExecutorService extends AbstractExecutorService { - private boolean isShutdown; - - @Override - public void shutdown() { - isShutdown = true; - } - - @NotNull - @Override - public List shutdownNow() { - isShutdown = true; - return List.of(); - } - - @Override - public boolean isShutdown() { - return isShutdown; - } - - @Override - public boolean isTerminated() { - return isShutdown; - } - - @Override - public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException { - return true; - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } -} diff --git a/src/main/java/org/embeddedt/modernfix/util/SingleThreadedWorkerService.java b/src/main/java/org/embeddedt/modernfix/util/SingleThreadedWorkerService.java new file mode 100644 index 00000000..ebc57823 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/util/SingleThreadedWorkerService.java @@ -0,0 +1,63 @@ +package org.embeddedt.modernfix.util; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Like {@link Executors#newSingleThreadExecutor()}, but handles the case where the background executor schedules + * a task to itself and waits for it the way a direct executor would. + */ +public class SingleThreadedWorkerService extends AbstractExecutorService { + private final AtomicReference thread = new AtomicReference<>(); + private final ExecutorService executorService; + + public SingleThreadedWorkerService() { + this.executorService = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "Worker-Main"); + t.setPriority(Thread.MIN_PRIORITY); + thread.set(t); + return t; + }); + } + + @Override + public void shutdown() { + executorService.shutdown(); + } + + @NotNull + @Override + public List shutdownNow() { + return executorService.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return executorService.isShutdown(); + } + + @Override + public boolean isTerminated() { + return executorService.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException { + return executorService.awaitTermination(timeout, unit); + } + + @Override + public void execute(@NotNull Runnable command) { + if (Thread.currentThread() == thread.get()) { + command.run(); + } else { + executorService.execute(command); + } + } +}