From 57b84509da7e87d134099cce2b498ff7b7f8d85e Mon Sep 17 00:00:00 2001 From: games647 Date: Sat, 14 Mar 2020 18:20:34 +0100 Subject: [PATCH] Limit the total number of running threads (Related #304) --- .../fastlogin/bukkit/FastLoginBukkit.java | 9 +++++ .../bukkit/command/CrackedCommand.java | 5 +-- .../bukkit/command/PremiumCommand.java | 5 +-- .../protocollib/ProtocolLibListener.java | 5 +-- bukkit/src/main/resources/plugin.yml | 17 +++----- .../fastlogin/bungee/FastLoginBungee.java | 2 +- .../bungee/listener/ConnectListener.java | 5 +-- .../listener/PluginMessageListener.java | 7 ++-- .../fastlogin/core/AsyncScheduler.java | 40 +++++++++++++++++++ .../games647/fastlogin/core/AuthStorage.java | 5 ++- .../fastlogin/core/shared/FastLoginCore.java | 10 +++++ .../fastlogin/core/shared/PlatformPlugin.java | 9 ++++- 12 files changed, 88 insertions(+), 31 deletions(-) create mode 100644 core/src/main/java/com/github/games647/fastlogin/core/AsyncScheduler.java diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java index 16cea7b4..05bb3860 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java @@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentMap; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.messaging.PluginMessageRecipient; @@ -68,6 +69,14 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin { + plugin.getCore().getAsyncScheduler().runAsync(() -> { plugin.getCore().getStorage().save(profile); }); } else { @@ -76,7 +75,7 @@ public class CrackedCommand extends ToggleCommand { plugin.getCore().sendLocaleMessage("remove-premium", sender); profile.setPremium(false); - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + plugin.getCore().getAsyncScheduler().runAsync(() -> { plugin.getCore().getStorage().save(profile); }); } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/command/PremiumCommand.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/command/PremiumCommand.java index 5483856d..190848e8 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/command/PremiumCommand.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/command/PremiumCommand.java @@ -5,7 +5,6 @@ import com.github.games647.fastlogin.core.StoredProfile; import java.util.UUID; -import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -56,7 +55,7 @@ public class PremiumCommand extends ToggleCommand { } else { //todo: resolve uuid profile.setPremium(true); - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + plugin.getCore().getAsyncScheduler().runAsync(() -> { plugin.getCore().getStorage().save(profile); }); @@ -85,7 +84,7 @@ public class PremiumCommand extends ToggleCommand { } else { //todo: resolve uuid profile.setPremium(true); - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + plugin.getCore().getAsyncScheduler().runAsync(() -> { plugin.getCore().getStorage().save(profile); }); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index 327b8989..e8525e6d 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -11,7 +11,6 @@ import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import java.security.KeyPair; import java.security.SecureRandom; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import static com.comphenix.protocol.PacketType.Login.Client.ENCRYPTION_BEGIN; @@ -67,7 +66,7 @@ public class ProtocolLibListener extends PacketAdapter { packetEvent.getAsyncMarker().incrementProcessingDelay(); Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair); - Bukkit.getScheduler().runTaskAsynchronously(plugin, verifyTask); + plugin.getCore().getAsyncScheduler().runAsync(verifyTask); } private void onLogin(PacketEvent packetEvent, Player player) { @@ -85,6 +84,6 @@ public class ProtocolLibListener extends PacketAdapter { packetEvent.getAsyncMarker().incrementProcessingDelay(); Runnable nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username, keyPair.getPublic()); - Bukkit.getScheduler().runTaskAsynchronously(plugin, nameCheckTask); + plugin.getCore().getAsyncScheduler().runAsync(nameCheckTask); } } diff --git a/bukkit/src/main/resources/plugin.yml b/bukkit/src/main/resources/plugin.yml index 29108a4e..db32c375 100644 --- a/bukkit/src/main/resources/plugin.yml +++ b/bukkit/src/main/resources/plugin.yml @@ -11,21 +11,14 @@ description: | website: ${project.url} dev-url: ${project.url} +# Load the plugin as early as possible to inject it for all players +load: STARTUP + # This plugin don't have to be transformed for compatibility with Minecraft >= 1.13 api-version: '1.13' -softdepend: - # We depend either ProtocolLib or ProtocolSupport - - ProtocolSupport - - ProtocolLib - - PlaceholderAPI - # Auth plugins - - AuthMe - - LoginSecurity - - xAuth - - LogIt - - UltraAuth - - CrazyLogin +# We depend either ProtocolLib or ProtocolSupport +depend: [ProtocolLib] commands: ${project.parent.name}: diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java index d48b73a6..2d0874d4 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java @@ -117,7 +117,7 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin readMessage(forPlayer, channel, data)); + plugin.getCore().getAsyncScheduler().runAsync(() -> readMessage(forPlayer, channel, data)); } private void readMessage(ProxiedPlayer forPlayer, String channel, byte[] data) { @@ -82,10 +81,10 @@ public class PluginMessageListener implements Listener { core.getPendingConfirms().remove(forPlayer.getUniqueId()); Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker); - ProxyServer.getInstance().getScheduler().runAsync(plugin, task); + plugin.getCore().getAsyncScheduler().runAsync(task); } else { Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, false, isSourceInvoker); - ProxyServer.getInstance().getScheduler().runAsync(plugin, task); + plugin.getCore().getAsyncScheduler().runAsync(task); } } } diff --git a/core/src/main/java/com/github/games647/fastlogin/core/AsyncScheduler.java b/core/src/main/java/com/github/games647/fastlogin/core/AsyncScheduler.java new file mode 100644 index 00000000..86081299 --- /dev/null +++ b/core/src/main/java/com/github/games647/fastlogin/core/AsyncScheduler.java @@ -0,0 +1,40 @@ +package com.github.games647.fastlogin.core; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * This limits the number of threads that are used at maximum. Thread creation can be very heavy for the CPU and + * context switching between threads too. However we need many threads for blocking HTTP and database calls. + * Nevertheless this number can be further limited, because the number of actually working database threads + * is limited by the size of our database pool. The goal is to separate concerns into processing and blocking only + * threads. + */ +public class AsyncScheduler { + + // 30 threads are still too many - the optimal solution is to separate into processing and blocking threads + // where processing threads could only be max number of cores while blocking threads could be minimized using + // non-blocking I/O and a single event executor + private final ThreadPoolExecutor executorService; + + public AsyncScheduler(ThreadFactory threadFactory) { + executorService = new ThreadPoolExecutor(5, 30, + 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(1024), threadFactory); + } + + public void runAsync(Runnable task) { + executorService.execute(task); + } + + public void shutdown() { + executorService.shutdown(); + try { + executorService.awaitTermination(1, TimeUnit.MINUTES); + } catch (InterruptedException interruptEx) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/core/src/main/java/com/github/games647/fastlogin/core/AuthStorage.java b/core/src/main/java/com/github/games647/fastlogin/core/AuthStorage.java index 7b02d23c..cd09262d 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/AuthStorage.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/AuthStorage.java @@ -72,6 +72,9 @@ public class AuthStorage { } public void createTables() throws SQLException { + // choose surrogate PK(ID), because UUID can be null for offline players + // if UUID is always Premium UUID we would have to update offline player entries on insert + // name cannot be PK, because it can be changed for premium players String createDataStmt = "CREATE TABLE IF NOT EXISTS `" + PREMIUM_TABLE + "` (" + "`UserID` INTEGER PRIMARY KEY AUTO_INCREMENT, " + "`UUID` CHAR(36), " @@ -87,7 +90,7 @@ public class AuthStorage { createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT"); } - //todo: add uuid index usage + //todo: add unique uuid index usage try (Connection con = dataSource.getConnection(); Statement createStmt = con.createStatement()) { createStmt.executeUpdate(createDataStmt); diff --git a/core/src/main/java/com/github/games647/fastlogin/core/shared/FastLoginCore.java b/core/src/main/java/com/github/games647/fastlogin/core/shared/FastLoginCore.java index 7ad24f34..eefecc9e 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/shared/FastLoginCore.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/shared/FastLoginCore.java @@ -4,6 +4,7 @@ import com.github.games647.craftapi.resolver.MojangResolver; import com.github.games647.craftapi.resolver.http.RotatingProxySelector; import com.github.games647.fastlogin.core.AuthStorage; import com.github.games647.fastlogin.core.CommonUtil; +import com.github.games647.fastlogin.core.AsyncScheduler; import com.github.games647.fastlogin.core.hooks.AuthPlugin; import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator; import com.github.games647.fastlogin.core.hooks.PasswordGenerator; @@ -51,6 +52,7 @@ public class FastLoginCore

> { private final T plugin; private final MojangResolver resolver = new MojangResolver(); + private final AsyncScheduler scheduler; private Configuration config; private AuthStorage storage; @@ -59,6 +61,7 @@ public class FastLoginCore

> { public FastLoginCore(T plugin) { this.plugin = plugin; + this.scheduler = new AsyncScheduler(plugin.getThreadFactory()); } public void load() { @@ -239,7 +242,14 @@ public class FastLoginCore

> { } } + public AsyncScheduler getAsyncScheduler() { + return scheduler; + } + public void close() { + plugin.getLog().info("Safely shutting down scheduler. This could take up to one minute."); + scheduler.shutdown(); + if (storage != null) { storage.close(); } diff --git a/core/src/main/java/com/github/games647/fastlogin/core/shared/PlatformPlugin.java b/core/src/main/java/com/github/games647/fastlogin/core/shared/PlatformPlugin.java index eeb622f7..9ecbccfc 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/shared/PlatformPlugin.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/shared/PlatformPlugin.java @@ -1,5 +1,7 @@ package com.github.games647.fastlogin.core.shared; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + import java.nio.file.Path; import java.util.concurrent.ThreadFactory; @@ -22,6 +24,11 @@ public interface PlatformPlugin { } default ThreadFactory getThreadFactory() { - return null; + return new ThreadFactoryBuilder() + .setNameFormat(getName() + " Pool Thread #%1$d") + // Hikari create daemons by default and we could daemon threads for our own scheduler too + // because we safely shutdown + .setDaemon(true) + .build(); } }