diff options
4 files changed, 95 insertions, 12 deletions
diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java index a721adc..53454aa 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java @@ -64,7 +64,9 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; import java.util.stream.Collectors; import static net.kyori.adventure.text.Component.space; @@ -262,12 +264,59 @@ public class SparkPlatform { } public void executeCommand(CommandSender sender, String[] args) { + AtomicReference<Thread> executorThread = new AtomicReference<>(); + AtomicReference<Thread> timeoutThread = new AtomicReference<>(); + AtomicBoolean completed = new AtomicBoolean(false); + + // execute the command this.plugin.executeAsync(() -> { + executorThread.set(Thread.currentThread()); this.commandExecuteLock.lock(); try { executeCommand0(sender, args); } finally { this.commandExecuteLock.unlock(); + executorThread.set(null); + completed.set(true); + + Thread timeout = timeoutThread.get(); + if (timeout != null) { + timeout.interrupt(); + } + } + }); + + // schedule a task to detect timeouts + this.plugin.executeAsync(() -> { + timeoutThread.set(Thread.currentThread()); + try { + for (int i = 1; i <= 3; i++) { + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + // ignore + } + + if (completed.get()) { + return; + } + + Thread executor = executorThread.get(); + if (executor == null) { + getPlugin().log(Level.WARNING, "A command execution has not completed after " + + (i * 5) + " seconds but there is no executor present. Perhaps the executor shutdown?"); + + } else { + String stackTrace = Arrays.stream(executor.getStackTrace()) + .map(el -> " " + el.toString()) + .collect(Collectors.joining("\n")); + + getPlugin().log(Level.WARNING, "A command execution has not completed after " + + (i * 5) + " seconds, it might be stuck. Trace: \n" + stackTrace); + } + } + } finally { + timeoutThread.set(null); } }); } diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/SparkThreadFactory.java b/spark-common/src/main/java/me/lucko/spark/common/util/SparkThreadFactory.java new file mode 100644 index 0000000..9c7309d --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/util/SparkThreadFactory.java @@ -0,0 +1,42 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) <luck@lucko.me> + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package me.lucko.spark.common.util; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +public class SparkThreadFactory implements ThreadFactory { + private static final AtomicInteger poolNumber = new AtomicInteger(1); + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + public SparkThreadFactory() { + this.namePrefix = "spark-worker-pool-" + + poolNumber.getAndIncrement() + + "-thread-"; + } + + public Thread newThread(Runnable r) { + Thread t = new Thread(r, this.namePrefix + this.threadNumber.getAndIncrement()); + t.setDaemon(true); + return t; + } +} diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java index 4bcfce4..7b0af11 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java @@ -35,6 +35,7 @@ import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.util.SparkThreadFactory; import me.lucko.spark.fabric.FabricClassSourceLookup; import me.lucko.spark.fabric.FabricSparkMod; @@ -60,12 +61,7 @@ public abstract class FabricSparkPlugin implements SparkPlugin { protected FabricSparkPlugin(FabricSparkMod mod) { this.mod = mod; this.logger = LogManager.getLogger("spark"); - this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> { - Thread thread = Executors.defaultThreadFactory().newThread(r); - thread.setName("spark-fabric-async-worker"); - thread.setDaemon(true); - return thread; - }); + this.scheduler = Executors.newScheduledThreadPool(4, new SparkThreadFactory()); this.platform = new SparkPlatform(this); } diff --git a/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeSparkPlugin.java b/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeSparkPlugin.java index f8b7559..7805935 100644 --- a/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeSparkPlugin.java +++ b/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeSparkPlugin.java @@ -35,6 +35,7 @@ import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.util.SparkThreadFactory; import me.lucko.spark.forge.ForgeClassSourceLookup; import me.lucko.spark.forge.ForgeSparkMod; @@ -79,12 +80,7 @@ public abstract class ForgeSparkPlugin implements SparkPlugin { protected ForgeSparkPlugin(ForgeSparkMod mod) { this.mod = mod; this.logger = LogManager.getLogger("spark"); - this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> { - Thread thread = Executors.defaultThreadFactory().newThread(r); - thread.setName("spark-forge-async-worker"); - thread.setDaemon(true); - return thread; - }); + this.scheduler = Executors.newScheduledThreadPool(4, new SparkThreadFactory()); this.platform = new SparkPlatform(this); } |