From 4bde0996ce9dc73592ad6292225f0a299b7f174b Mon Sep 17 00:00:00 2001 From: Lixfel Date: Mon, 15 Jan 2024 17:34:14 +0100 Subject: [PATCH] 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); + } + } + } +}