From 4bde0996ce9dc73592ad6292225f0a299b7f174b Mon Sep 17 00:00:00 2001 From: Lixfel Date: Mon, 15 Jan 2024 17:34:14 +0100 Subject: [PATCH 1/2] First untested checkpoint implementation --- .../comphenix/tinyprotocol/TinyProtocol.java | 7 +- .../src/de/steamwar/core/CheckpointUtils.java | 85 +++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java diff --git a/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java b/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java index 7957a0f..2528a6f 100644 --- a/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java +++ b/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java @@ -28,8 +28,11 @@ public class TinyProtocol implements Listener { private static final Class playerList = Reflection.getClass("{nms.server.players}.PlayerList"); private static final Class minecraftServer = Reflection.getClass("{nms.server}.MinecraftServer"); private static final FieldAccessor getMinecraftServer = Reflection.getField(playerList, minecraftServer, 0); - private static final Class serverConnection = Reflection.getClass("{nms.server.network}.ServerConnection"); + public static final Class serverConnection = Reflection.getClass("{nms.server.network}.ServerConnection"); private static final FieldAccessor getServerConnection = Reflection.getField(minecraftServer, serverConnection, 0); + public static Object getServerConnection(Plugin plugin) { + 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); @@ -51,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.get(getMinecraftServer.get(getPlayerList.get(plugin.getServer())))); + this.connections = getConnections.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 new file mode 100644 index 0000000..cac5a2d --- /dev/null +++ b/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java @@ -0,0 +1,85 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; +import de.steamwar.sql.internal.Statement; +import io.netty.channel.ChannelFuture; +import org.bukkit.Bukkit; +import org.eclipse.openj9.criu.CRIUSupport; +import org.eclipse.openj9.criu.JVMCRIUException; + +import java.io.DataInputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +public class 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() { + Bukkit.getOnlinePlayers().forEach(player -> player.kickPlayer(null)); + Statement.closeAll(); + + // Close socket + Object serverConnection = TinyProtocol.getServerConnection(Core.getInstance()); + List channels = channelFutures.get(serverConnection); + for(Object future : channels) { + ((ChannelFuture) future).channel().close().syncUninterruptibly(); + } + channels.clear(); + + // Do the snapshot + Path path = FileSystems.getDefault().getPath("/tmp/checkpoints/" + Core.getVersion() + "/" + Bukkit.getWorlds().get(0).getName()); + path.toFile().mkdirs(); + CRIUSupport criu = new CRIUSupport(path); + criu.setAutoDedup(true); + //criu.setShellJob(true); + //criu.setWorkDir(path); criu.setLogFile("criu.log"); + try { + criu.checkpointJVM(); + } catch (JVMCRIUException e) { + Bukkit.shutdown(); + throw e; + } + + // Get new port + int port; + try (DataInputStream stream = new DataInputStream(Files.newInputStream(path.resolve("port").toFile().toPath()))) { + port = stream.readInt(); + } catch (IOException e) { + Bukkit.shutdown(); + throw new SecurityException(e); + } + + // Reopen socket + bind.invoke(serverConnection, InetAddress.getLoopbackAddress(), port); + if(Core.getVersion() > 12) { + for(Object future : channels) { + ((ChannelFuture) future).channel().config().setAutoRead(true); + } + } + } +} -- 2.39.2 From c0b111eabc0ffaf94b8e314dade431b6e737cbfd Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 16 Jan 2024 12:36:49 +0100 Subject: [PATCH 2/2] First tested checkpoint implementation --- .../src/de/steamwar/core/CheckpointUtils.java | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java b/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java index cac5a2d..8cedf02 100644 --- a/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java +++ b/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java @@ -21,6 +21,7 @@ package de.steamwar.core; import com.comphenix.tinyprotocol.Reflection; import com.comphenix.tinyprotocol.TinyProtocol; +import com.viaversion.viaversion.api.Via; import de.steamwar.sql.internal.Statement; import io.netty.channel.ChannelFuture; import org.bukkit.Bukkit; @@ -28,22 +29,46 @@ import org.eclipse.openj9.criu.CRIUSupport; import org.eclipse.openj9.criu.JVMCRIUException; import java.io.DataInputStream; +import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Comparator; import java.util.List; +import java.util.logging.Level; +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() { - Bukkit.getOnlinePlayers().forEach(player -> player.kickPlayer(null)); + 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 + } + } + } + + private static void freezeInternal(Path path) throws Exception { Statement.closeAll(); // Close socket + Via.getManager().getInjector().uninject(); Object serverConnection = TinyProtocol.getServerConnection(Core.getInstance()); List channels = channelFutures.get(serverConnection); for(Object future : channels) { @@ -51,13 +76,13 @@ public class CheckpointUtils { } channels.clear(); - // Do the snapshot - Path path = FileSystems.getDefault().getPath("/tmp/checkpoints/" + Core.getVersion() + "/" + Bukkit.getWorlds().get(0).getName()); + // Do the checkpoint path.toFile().mkdirs(); CRIUSupport criu = new CRIUSupport(path); criu.setAutoDedup(true); - //criu.setShellJob(true); - //criu.setWorkDir(path); criu.setLogFile("criu.log"); + criu.setFileLocks(true); + criu.setShellJob(true); + criu.setLogFile("criu.log"); try { criu.checkpointJVM(); } catch (JVMCRIUException e) { @@ -69,9 +94,6 @@ public class CheckpointUtils { int port; try (DataInputStream stream = new DataInputStream(Files.newInputStream(path.resolve("port").toFile().toPath()))) { port = stream.readInt(); - } catch (IOException e) { - Bukkit.shutdown(); - throw new SecurityException(e); } // Reopen socket @@ -81,5 +103,8 @@ public class CheckpointUtils { ((ChannelFuture) future).channel().config().setAutoRead(true); } } + Via.getManager().getInjector().inject(); + + Core.getInstance().getLogger().log(Level.INFO, "Checkpoint restored"); } } -- 2.39.2