From 2b779ab40b47f21938ca8b36cb07c83ae71a2e5d Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 16 Jan 2024 22:02:05 +0100 Subject: [PATCH 1/3] Use signal handler for checkpointing --- .../comphenix/tinyprotocol/TinyProtocol.java | 4 +- .../src/de/steamwar/core/CheckpointUtils.java | 60 +++++++++++++------ .../src/de/steamwar/core/Core.java | 1 + 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java b/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java index 2528a6f..caa2bb8 100644 --- a/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java +++ b/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java @@ -34,7 +34,7 @@ public class TinyProtocol implements Listener { return getServerConnection.get(getMinecraftServer.get(getPlayerList.get(plugin.getServer()))); } private static final Class networkManager = Reflection.getClass("{nms.network}.NetworkManager"); - private static final FieldAccessor getConnections = Reflection.getField(serverConnection, List.class, 0, networkManager); + public static final FieldAccessor networkManagers = Reflection.getField(serverConnection, List.class, 0, networkManager); public static final TinyProtocol instance = new TinyProtocol(Core.getInstance()); private static int id = 0; @@ -54,7 +54,7 @@ public class TinyProtocol implements Listener { private TinyProtocol(final Plugin plugin) { this.plugin = plugin; this.handlerName = "tiny-" + plugin.getName() + "-" + ++id; - this.connections = getConnections.get(getServerConnection(plugin)); + this.connections = networkManagers.get(getServerConnection(plugin)); plugin.getServer().getPluginManager().registerEvents(this, plugin); diff --git a/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java b/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java index 8cedf02..771ea23 100644 --- a/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java +++ b/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java @@ -25,8 +25,10 @@ import com.viaversion.viaversion.api.Via; import de.steamwar.sql.internal.Statement; import io.netty.channel.ChannelFuture; import org.bukkit.Bukkit; +import org.bukkit.Chunk; import org.eclipse.openj9.criu.CRIUSupport; import org.eclipse.openj9.criu.JVMCRIUException; +import sun.misc.Signal; import java.io.DataInputStream; import java.io.File; @@ -35,6 +37,7 @@ import java.net.InetAddress; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.logging.Level; @@ -43,28 +46,47 @@ import java.util.stream.Stream; public class CheckpointUtils { private CheckpointUtils() {} - private static final Reflection.FieldAccessor channelFutures = Reflection.getField(TinyProtocol.serverConnection, List.class, 0, ChannelFuture.class); - private static final Reflection.MethodInvoker bind = Reflection.getMethod(TinyProtocol.serverConnection, null, InetAddress.class, int.class); - - public static void freeze() { - Path path = FileSystems.getDefault().getPath(System.getProperty("checkpoint")); - - try { - freezeInternal(path); - } catch (Exception e) { - Bukkit.shutdown(); - throw new SecurityException(e); - } finally { - // Delete checkpoint - try (Stream stream = Files.walk(path)) { - stream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); - } catch (IOException e) { - //ignore - } - } + public static void signalHandler() { + Signal.handle(new Signal("USR1"), signal -> Bukkit.getScheduler().runTask(Core.getInstance(), CheckpointUtils::freeze)); } + public static void freeze() { + String checkpointFile = System.getProperty("checkpoint"); + if(!CRIUSupport.isCheckpointAllowed() || checkpointFile == null) { + Bukkit.shutdown(); + return; + } + + Bukkit.getOnlinePlayers().forEach(player -> player.kickPlayer(null)); + + List networkManagers = TinyProtocol.networkManagers.get(TinyProtocol.getServerConnection(Core.getInstance())); + if(networkManagers.isEmpty()) { + Path path = FileSystems.getDefault().getPath(checkpointFile); + + try { + freezeInternal(path); + } catch (Exception e) { + Bukkit.shutdown(); + throw new SecurityException(e); + } finally { + // Delete checkpoint + try (Stream stream = Files.walk(path)) { + stream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } catch (IOException e) { + //ignore + } + } + return; + } + + Core.getInstance().getLogger().log(Level.INFO, "Waiting for players to disconnect for checkpointing"); + Bukkit.getScheduler().runTaskLater(Core.getInstance(), CheckpointUtils::freeze, 1); + } + + private static final Reflection.FieldAccessor channelFutures = Reflection.getField(TinyProtocol.serverConnection, List.class, 0, ChannelFuture.class); + private static final Reflection.MethodInvoker bind = Reflection.getMethod(TinyProtocol.serverConnection, null, InetAddress.class, int.class); private static void freezeInternal(Path path) throws Exception { + Bukkit.getWorlds().forEach(world -> Arrays.stream(world.getLoadedChunks()).forEach(Chunk::unload)); Statement.closeAll(); // Close socket diff --git a/SpigotCore_Main/src/de/steamwar/core/Core.java b/SpigotCore_Main/src/de/steamwar/core/Core.java index 7d3a30d..7e0b371 100644 --- a/SpigotCore_Main/src/de/steamwar/core/Core.java +++ b/SpigotCore_Main/src/de/steamwar/core/Core.java @@ -97,6 +97,7 @@ public class Core extends JavaPlugin{ AuthlibInjector.inject(); TinyProtocol.init(); + CheckpointUtils.signalHandler(); new AntiNocom(); if(Core.getVersion() < 17 && Bukkit.getPluginManager().getPlugin("ViaVersion") != null) From 76799982850e5e21c86640d7177da0e20152caf1 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 16 Jan 2024 22:18:22 +0100 Subject: [PATCH 2/3] Invert condition --- .../src/de/steamwar/core/CheckpointUtils.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java b/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java index 771ea23..bb7ba3d 100644 --- a/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java +++ b/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java @@ -60,27 +60,27 @@ public class CheckpointUtils { Bukkit.getOnlinePlayers().forEach(player -> player.kickPlayer(null)); List networkManagers = TinyProtocol.networkManagers.get(TinyProtocol.getServerConnection(Core.getInstance())); - if(networkManagers.isEmpty()) { - Path path = FileSystems.getDefault().getPath(checkpointFile); - - try { - freezeInternal(path); - } catch (Exception e) { - Bukkit.shutdown(); - throw new SecurityException(e); - } finally { - // Delete checkpoint - try (Stream stream = Files.walk(path)) { - stream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); - } catch (IOException e) { - //ignore - } - } + if(!networkManagers.isEmpty()) { + Core.getInstance().getLogger().log(Level.INFO, "Waiting for players to disconnect for checkpointing"); + Bukkit.getScheduler().runTaskLater(Core.getInstance(), CheckpointUtils::freeze, 1); return; } - Core.getInstance().getLogger().log(Level.INFO, "Waiting for players to disconnect for checkpointing"); - Bukkit.getScheduler().runTaskLater(Core.getInstance(), CheckpointUtils::freeze, 1); + Path path = FileSystems.getDefault().getPath(checkpointFile); + + try { + freezeInternal(path); + } catch (Exception e) { + Bukkit.shutdown(); + throw new SecurityException(e); + } finally { + // Delete checkpoint + try (Stream stream = Files.walk(path)) { + stream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } catch (IOException e) { + //ignore + } + } } private static final Reflection.FieldAccessor channelFutures = Reflection.getField(TinyProtocol.serverConnection, List.class, 0, ChannelFuture.class); From 1fdb7d8e0dbdde9f39d61bd53c4c18f026c7630d Mon Sep 17 00:00:00 2001 From: Lixfel Date: Wed, 17 Jan 2024 12:13:33 +0100 Subject: [PATCH 3/3] Add logfile to exception --- .../src/de/steamwar/core/CheckpointUtils.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java b/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java index bb7ba3d..13feea8 100644 --- a/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java +++ b/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java @@ -30,9 +30,7 @@ import org.eclipse.openj9.criu.CRIUSupport; import org.eclipse.openj9.criu.JVMCRIUException; import sun.misc.Signal; -import java.io.DataInputStream; -import java.io.File; -import java.io.IOException; +import java.io.*; import java.net.InetAddress; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -109,6 +107,11 @@ public class CheckpointUtils { criu.checkpointJVM(); } catch (JVMCRIUException e) { Bukkit.shutdown(); + + Path logfile = path.resolve("criu.log"); + if(logfile.toFile().exists()) + throw new IllegalStateException("Could not create checkpoint, criu log:\n" + new String(Files.readAllBytes(logfile)), e); + throw e; }